这些题目本质上都跟单例模式相关。
单例模式就是保证一个类只有一个实例,并提供一个访问它的全局访问点。首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。
下边就是一个常见的单例模式程序例子:
// 程序1
1 class Singleton
3 private:
4 Singleton(){}
5 ~Singleton(){}
6 static Singleton *pInstance;
8 public:
9 static Singleton *GetInstance() // 对GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。
10 {
11 if (pInstance == NULL) //判断是否第一次调用
12 {
13 pInstance = new Singleton ();
14 }
15 return pInstance;
16 }
18 static void DestoryInstance()
19 {
20 if (pInstance != NULL)
21 {
22 delete pInstance;
23 pInstance = NULL;
24 }
25 }
27 };
29 Singleton *Singleton ::pInstance = NULL;
该程序保证在不调用类中的静态函数的情况下,不能够在类外创建该类的实例(因为构造函数为私有函数);另外,在非多线程模式下只能创建该类的一个实例。
1. 因为上述构造函数
或
析构函数为私有函数,所以该类是无法被继承的,满足文章开头提到的第二题。
2. 该类的实例只能被创建在堆上(new),因为析构函数被声明为私有函数,满足文章开头提到的第三题。具体原因摘自博文
如何限制对象只能建立在堆上或者栈上
:
在C++中,类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* ptr=new A;这两种方式是有区别的。
静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,直接调用类的构造函数。
动态建立类对象,是使用new运算符将对象建立在堆空间中。这个过程分为两步,第一步是执行operator new()函数,在堆空间中搜索合适的内存并进行分配;第二步是调用构造函数构造对象,初始化这片内存空间。这种方法,间接调用类的构造函数。
... ...
类对象只能建立在堆上,就是不能静态建立类对象,即不能直接调用类的构造函数。
容易想到将构造函数设为私有。在构造函数私有之后,无法在类外部调用构造函数来构造类对象,只能使用new运算符来建立对象。然而,前面已经说过,new运算符的执行过程分为两步,C++提供new运算符的重载,其实是只允许重载operator new()函数,而operator()函数用于分配内存,无法提供构造功能。因此,这种方法不可以。
当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数,情况会是怎样的呢?比如,类的析构函数是私有的,编译器无法调用析构函数来释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。
如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。
... ...
只有使用new运算符,对象才会建立在堆上,因此,
只要禁用new运算符就可以实现类对象只能建立在栈上。
将operator new()设为私有即可。代码如下:
1 class A
3 private:
4 void* operator new(size_t t){} // 注意函数的第一个参数和返回值都是固定的
5 void operator delete(void* ptr){} // 重载了new就需要重载delete
6 public:
7 A(){}
8 ~A(){}
自动析构实例
我们知道,对于类Singleton的实例,最后我们需要显式调用DestroyInstance函数来释放内存。那有没有一种方法可以让程序自动析构实例呢?
要自动析构实例,这里我们需要用到C++中的RAII(Resource Acquisition Is Initialization)机制。具体地,我们在类Singleton中在声明一个静态类(析构函数释放Singleton实例内存)并定义一个该类的静态实例。这样,在Singleton实例被析构时,该静态实例的析构函数会被自动调用,所以最终能够将Singleton实例的内存自动释放掉。具体程序如下:
// 程序2
1 class Singleton
3 private:
4 Singleton(){}
5 ~Singleton(){}
6 static Singleton *pInstance;
8 class Garbo //它的唯一工作就是在析构函数中删除Singleton的实例
10 public:
11 ~Garbo()
12 {
13 if (pInstance != NULL)
14 {
15 delete pInstance;
16 pInstance = NULL;
17 cout << "Delete instance!" << endl;
18 }
19 }
20 };
21 static Garbo garbo; //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
23 public:
24 static Singleton *GetInstance() // 对GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。
25 {
26 if (pInstance == NULL) //判断是否第一次调用
27 {
28 pInstance = new Singleton();
29 cout << "Create instance" << endl;
30 }
31 return pInstance;
32 }
34 };
36 Singleton *Singleton::pInstance = NULL;
37 Singleton::Garbo Singleton::garbo;
这个程序可能会显得麻烦臃肿,我们可以改进成这个样子:
// 程序3
1 class Singleton
3 private:
4 Singleton(){} // 构造函数是私有的
5 // ~Singleton(){} // 在这里不可以声明为private。因为我们在函数GetInstance声明定义了位于栈上的变量,
6 // 这样程序结束时会自动调用析构函数(为private则调用不了,编译不通过).
8 public:
9 static Singleton& GetInstance()
10 {
11 static Singleton instance; // 局部静态变量
12 return instance;
13 }
14 };
16 int main()
17 {
18 Singleton singleton1 = Singleton::GetInstance();
19 Singleton singleton2 = singleton1;
20 cout << &singleton1 << endl;
21 cout << &singleton2 << endl;
23 return 0;
这一下,程序简洁又能够在程序运行结束时自动释放实例内存。但我们发现,在测试(main函数)时,我们发现singleton1和singleton2的地址并不一样,也就是说,这个程序存在漏洞,即通过默认拷贝函数可以生成不止一个类的实例。不过我们可以考虑将默认拷贝函数和默认赋值函数权限设定为private或protect:
// 程序4
1 class Singleton
3 private:
4 Singleton(){} // 构造函数是私有的
5 Singleton(const Singleton& orig){};
6 Singleton& operator=(const Singleton& orig){};
7 // ~Singleton(){} // 在这里不可以声明为private。因为我们在函数GetInstance声明定义了位于栈上的变量,
8 // 这样程序结束时会自动调用析构函数(为private则调用不了,编译不通过).
10 public:
11 static Singleton& GetInstance()
12 {
13 static Singleton instance; // 局部静态变量
14 return instance;
15 }
16 };
18 int main()
19 {
20 Singleton singleton1 = Singleton::GetInstance(); // 通不过编译,实际会调用默认拷贝函数
21 Singleton singleton2 = singleton1; // 通不过编译,因为会调用默认拷贝函数
22 cout << &singleton1 << endl;
23 cout << &singleton2 << endl;
25 return 0;
接下来,我们继续改进这个程序:
// 程序5
1 class Singleton
3 private:
4 Singleton(){} // 构造函数是私有的
5 // ~Singleton(){} // 在这里不可以声明为private。因为我们在函数GetInstance声明定义了位于栈上的变量,
6 // 这样程序结束时会自动调用析构函数(为private则调用不了,编译不通过).
8 public:
9 ~Singleton(){ cout << "~Singleton is called!" << endl; }
10 static Singleton* GetInstance()
11 {
12 static Singleton instance; // 局部静态变量
13 return &instance;
14 }
15 };
17 int main()
18 {
19 Singleton *singleton1 = Singleton::GetInstance();
20 Singleton *singleton2 = singleton1;
21 Singleton *singleton3 = Singleton::GetInstance();
22 cout << singleton1 << endl;
23 cout << singleton2 << endl;
24 cout << singleton3 << endl;
26 return 0;
程序运行结果如下:
结果证明了最后改进的这个程序能够只生成一个类的实例,而且在程序结束时能够自动调用析构函数释放内存。
考虑多线程
对于程序5而言,不存在线程竞争的问题;但对程序1和程序2而言是存在这个问题的。这里以程序2为例来说明如何避免线程竞争:
1 class Singleton
3 private:
4 Singleton(){}
5 ~Singleton(){}
6 static Singleton *pInstance;
8 class Garbo //它的唯一工作就是在析构函数中删除Singleton的实例
10 public:
11 ~Garbo()
12 {
13 if (pInstance != NULL)
14 {
15 Lock();
16 if (pInstance != NULL)
17 {
18 delete pInstance;
19 pInstance = NULL;
20 cout << "Delete instance!" << endl;
21 }
22 Unlock();
23 }
24 }
25 };
26 static Garbo garbo; //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
28 public:
29 static Singleton *GetInstance() // 对GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。
30 {
31 if (pInstance == NULL) //判断是否第一次调用
32 {
33 Lock();
34 if (pInstance == NULL) // 此处进行了两次m_Instance == NULL的判断,是借鉴了Java的单例模式实现时,
35 // 使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,
36 // 而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。
37 {
38 pInstance = new Singleton();
39 cout << "Create instance" << endl;
40 }
41 Unlock();
42 }
43 return pInstance;
44 }
46 };
48 Singleton *Singleton::pInstance = NULL;
49 Singleton::Garbo Singleton::garbo;
C++中的单例模式
C++设计模式——单例模式
如何限制对象只能建立在堆上或者栈上