C++ Primer 学习笔记_27_类与数据抽象(9)--static 与单例模式、auto_ptr与单例模式、const成员函数、const 对象、mutable修饰符

C++ Primer 学习笔记_27_类与数据抽象(9)--static 与单例模式、auto_ptr与单例模式、const成员函数、const 对象、mutable修饰符

前言

【例】写出面向对象的五个基本原则

解答:单一职责原则,开放封闭原则,依赖倒置原则,接口隔离原则和里氏替换原则

里氏替换原则:子类型必须能够替换他们的基类型。

    设计模式分为三种类型:创建型模式、结构型模式和行为型模式


一、static 与单例模式

1、 单例模式的意图: 保证一个类仅有一个实例(对象),并提供一个访问它的全局访问点
(1)示例
#include <iostream>
using namespace std;
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (instance_ == NULL)
        {
            instance_ = new Singleton;
        }
        return instance_;
    }
    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
private:
    Singleton(const Singleton &other);  //将拷贝函数放在private,禁止拷贝
    Singleton &operator=(const Singleton &other);  //禁止赋值
    Singleton()  //禁止创建对象
    {
        cout << "Singleton ..." << endl;
    }
    static Singleton *instance_;
};
Singleton *Singleton::instance_;
int main(void)
{
    //Singleton s1;  // Error,调用默认构造函数
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
    //Singleton s3(*s1);        // Error,调用拷贝构造函数
    return 0;
}
运行结果:
Singleton ...
解释:只调用了一次构造函数“Singleton ...”,因此只生成了一个示例。但是这个程序有两个问题:第一个是该程序不会进行析构,会有资源泄露的问题第二是当前程序允许调用拷贝构造函数,一旦调用拷贝则生成了多个实例,因此需要禁止拷贝


2、禁止拷贝
    禁止拷贝有两个点:第一,将拷贝构造函数声明为私有,并不进行实现。第二,将赋值操作禁止,并不进行实现。

3、禁止拷贝,并解决上述代码的资源泄露问题
    为了解决对象不会被析构的问题,可以使用一个静态的嵌套类对象来解决:
    实现一个嵌套类,定义一个Grabo类型。然后在原有类中定义static的Garbo成员对象,这样在程序结束时,会调用Garbo的析构函数,从而执行析构函数。
(1)示例

#include <iostream>
using namespace std;
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (instance_ == NULL)
        {
            instance_ = new Singleton;
        }
        return instance_;
    }
    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
    class Garbo
    {
    public:
        ~Garbo()
        {
            if (Singleton::instance_ != NULL)
            {
                delete instance_;
            }
        }
    };
private:
    Singleton(const Singleton &other);  //将拷贝函数放在private,禁止拷贝
    Singleton &operator=(const Singleton &other);  //禁止赋值
    Singleton()  //禁止创建对象
    {
        cout << "Singleton ..." << endl;
    }
    static Singleton* instance_;
    static Garbo garbo_;    // 利用对象的确定性析构
};
Singleton::Garbo Singleton::garbo_;
Singleton* Singleton::instance_;
int main(void)
{
    //Singleton s1;  // Error,调用默认构造函数
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
    //Singleton s3(*s1);        // Error,调用拷贝构造函数
    return 0;
}
运行结果:
Singleton ...
~Singleton ...

解释:利用静态嵌套对象的确定性析构会调用Garbo类的析构函数,在析构函数内delete 单例类的指针。


4、上面办法比较繁琐,也可以返回局部静态对象的引用来解决:原因在于局部静态对象在运行期初始化,而且是有状态的。当前面已经定义过该对象的时候,后续会直接使用先前定义的对象,这样保证instance只有一个实例,第二次调用的时候直接使用先前定义的对象。因为返回了引用,因此也不会调用拷贝构造函数。

#include <iostream>
using namespace std;
class Singleton
{
public:
    static Singleton& GetInstance()  //返回引用
    {
        static Singleton instance;      // 局部静态对象
        return instance;
    }
    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
private:
    Singleton(const Singleton &other);  //将拷贝函数放在private,禁止拷贝
    Singleton &operator=(const Singleton &other);  //禁止赋值
    Singleton()  //禁止创建对象
    {
        cout << "Singleton ..." << endl;
    }
};
int main(void)
{
    Singleton& s1 = Singleton::GetInstance(); //这里也应该是引用,也可以是指针
    Singleton& s2 = Singleton::GetInstance();
    return 0;
}

运行结果:

Singleton ...
~Singleton ...

解释:局部静态对象只会初始化一次,所以调用多次GetInstance函数得到的是同一个对象。由于函数内使用了静态对象,故不是线程安全的。


5、实际上也可以使用auto_ptr 智能指针来解决,程序如下,更详细的对auto_ptr将在后续讨论。

#include <iostream>
#include<memory>
using namespace std;

class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (instance_.get() == NULL)
        {
            instance_ = auto_ptr<Singleton>(new Singleton);
        }
        return instance_.get();
    }

    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
private:
    Singleton(const Singleton &other);
    Singleton &operator=(const Singleton &other);
    Singleton()
    {
        cout << "Singleton ..." << endl;
    }
    static auto_ptr<Singleton> instance_;
};

auto_ptr<Singleton> Singleton::instance_;

int main(void)
{
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
    return 0;
}
运行结果:
Singleton ...
~Singleton ...

6、饿汉式单例模式(在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快)

    实际上,上述所有的单例模式例子都不是线程安全的,设想如果两个线程同时运行到语句if (instance == null),而此时该实例的确没有创建,那么两个线程都会创建一个实例。如果不希望加锁实现线程安全,可以使用饿汉模式(即在main函数之前先生成一个实例):

#include <iostream>
using namespace std;
class Singleton
{
public:
    static const Singleton* GetInstance()
    {
        return instance_;
    }
    ~Singleton()
    {
        cout << "~Singleton ..." << endl;
    }
    class Garbo
    {
    public:
        ~Garbo()
        {
            if (Singleton::instance_ != NULL)
            {
                delete instance_;
            }
        }
    };
private:
    Singleton(const Singleton &other);  //将拷贝函数放在private,禁止拷贝
    Singleton &operator=(const Singleton &other);  //禁止赋值
    Singleton()  //禁止创建对象
    {
        cout << "Singleton ..." << endl;
    }
    static const Singleton* instance_;
    static Garbo garbo_;    // 利用对象的确定性析构
};

const Singleton* Singleton::instance_ = new Singleton();
Singleton::Garbo Singleton::garbo_;

int main(void)
{
    //Singleton s1;  // Error,调用默认构造函数
    const Singleton *s1 = Singleton::GetInstance();
    const Singleton *s2 = Singleton::GetInstance();
    //Singleton s3(*s1);        // Error,调用拷贝构造函数
    return 0;
}

运行结果:
Singleton ...
~Singleton ...


7、或者通过加锁方式实现,详细将在后续讨论。



二、const成员函数、const 对象、mutable修饰符

1、const 成员函数

(1)const成员函数不会修改对象的状态

(2)const成员函数只能访问数据成员的值,而不能修改它

(3)const成员函数定义:

    类型 函数名() const

(4)示例
如果一个函数不会修改数据成员的值,尽量定义为const成员函数

#include <iostream>
using namespace std;
class Test
{
public:
     Test(int x) : x_(x), outputTimes_(0)
     {
     }
     int GetX() const  //如果一个函数不会修改数据成员的值,尽量定义为const成员函数
     {
          cout<<"const GetX ..."<<endl;
          //x_ = 100;
          return x_;
     }
     int GetX()  //const成员函数与非const成员函数可以构成重载
     {
          cout<<"GetX ..."<<endl;
          return x_;
     }
private:
     int x_;
};

int main(void)
{
     return 0;
}

2、const 对象

(1)如果把一个对象指定为const,就是告诉编译器不要修改它
(2)const对象的定义:

【1】const 类名 对象名(参数表);

(3)const对象不能调用非const成员函数


3、mutable修饰

(1)用mutable修饰的数据成员即使在const对象或在const成员函数中都可以被修改

(2)希望统计const成员函数void Output() const调用的次数,则可以使用mutable修饰。示例如下:

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int x) : x_(x), outputTimes_(0) { }
    
    int GetX() const
    {
        cout << "const GetX ..." << endl;
        //x_ = 100;  //Error,尝试修改数据成员    
        return x_;
    }

    int GetX()
    {
        cout << "GetX ..." << endl;
        return x_;
    }

    void Output() const
    {
        cout << "x=" << x_ << endl;
        outputTimes_++;
    }

    int GetOutputTimes() const
    {
        return outputTimes_;
    }
private:
    int x_;
    mutable int outputTimes_;  //mutable修饰
};

int main(void)
{
    const Test t(10);
    t.GetX();
    Test t2(20);
    t2.GetX();
    t.Output();
    t.Output();
    cout << t.GetOutputTimes() << endl;
    return 0;
}
运行结果:

const GetX ...
GetX ...
x=10
x=10
2

解释:如果注释掉int GetX() const函数,那么则会出错,因为const对象不能调用非const成员函数。即使const对象t调用了const成员函数Output()函数,mutable修饰的成员还是可以被修改。



参考:

C++ primer 第四版

你可能感兴趣的:(C++,C++,Primer,类与数据抽象)