单例模式
单例模式,就是指一个类有且仅有一个对象实例。
例如做一个学生成绩管理系统,我定义了一个“系统类”,用于存储数据,类似于“数据库”的功能。这个系统类当然只能有一个对象实例了;如果有多个实例,就会导致数据不同步。
1. 饿汉式
饿汉式,简单来讲,就是预先建立一个对象,设为 null ( Java中不可以这么写, C++中可以 ),不管什么时候需要用这个类,就返回这个唯一的对象引用或者指针。
(1)保证唯一:static
这个类的对象共享这一个 static 对象,
(2)保证只用这个 static 对象
把构造函数设为 private,需要用到这个类时,用一个函数 Get_instance () 得到 static 对象的引用或指针
#include <bits/stdc++.h> class Gragh { private: int data ; // 饿汉式, 线程安全 static Gragh null ; public: void display () const { std::cout << "data = " << data << std::endl ; } // error: cannot call member function without object // 要加 static ;非静态成员函数必须要有对象才能调用 static Gragh &Get_instance () { return null ; } private: // 构造函数声明成私有的 explicit Gragh () : data ( 100 ) {} } ; // undefined reference to null // 不在类外声明, 会报错 Gragh Gragh::null ; int main () { Gragh &One = Gragh::Get_instance () ; // 只能用 classname:: 因为现在还没有对象 One.display () ; std::cout << "One 的地址是 : " << &One << std::endl ; Gragh &Two = Gragh::Get_instance () ; Two.display () ; std::cout << "Two 的地址是 : " << &Two << std::endl ; return 0 ; }
可以发现,得到的两个引用的地址是一样的,再声明多少个引用都是一样的地址,这就保证了唯一;而且,如果直接声明构造函数 Gragh One ; 或者 Gragh *One = new Gragh () ,会编译错误,因为构造函数是私有的 private。
但是还是存在几个问题:拷贝构造函数,赋值,友元...... 等等
(3). 赋值,拷贝函数应该也要设置成 private, 禁止明显赋值或者拷贝
#include <bits/stdc++.h> class Gragh { private: int data ; // 饿汉式, 线程安全 static Gragh null ; public: void display () const { std::cout << "data = " << data << std::endl ; } // error: cannot call member function without object // 要加 static ;非静态成员函数必须要有对象才能调用 static Gragh &Get_instance () { return null ; } private: // 构造函数声明成私有的 explicit Gragh () : data ( 100 ) {} Gragh ( const Gragh &One ) {} // 拷贝构造函数 Gragh ( Gragh &One ) {} // 拷贝构造函数 Gragh & operator = ( const Gragh &One ) { // 赋值操作符 return null ; } } ; // undefined reference to null // 不在类外声明, 会报错 Gragh Gragh::null ; int main () { Gragh &One = Gragh::Get_instance () ; // 只能用 classname:: 因为现在还没有对象 One.display () ; std::cout << "One 的地址是 : " << &One << std::endl ; Gragh &Two = Gragh::Get_instance () ; Two.display () ; std::cout << "Two 的地址是 : " << &Two << std::endl ; // Gragh Three = One ; // 赋值函数是 private, 错误 // Gragh Four ( One ) ; // 拷贝构造函数是 private, 错误 return 0 ; }
有的公司还以此为编码规范
#include <bits/stdc++.h> class Gragh { // 宏定义 // 尽量别放在 .h 文件中, 防止污染 #define DISALLOW_COPY_AND_ASSIGN(Gragh) \ private: \ Gragh ( Gragh & ) ; \ Gragh ( const Gragh & ) ; \ Gragh & operator = (const Gragh & ) ; \ private: int data ; // 饿汉式, 线程安全 static Gragh null ; public: void display () const { std::cout << "data = " << data << std::endl ; } // error: cannot call member function without object // 要加 static ;非静态成员函数必须要有对象才能调用 static Gragh &Get_instance () { return null ; } private: // 构造函数声明成私有的 explicit Gragh () : data ( 100 ) {} // 一句话代表三种情况 DISALLOW_COPY_AND_ASSIGN ( Gragh ) ; } ; // undefined reference to null // 不在类外声明, 会报错 Gragh Gragh::null ; int main () { Gragh &One = Gragh::Get_instance () ; // 只能用 classname:: 因为现在还没有对象 One.display () ; std::cout << "One 的地址是 : " << &One << std::endl ; Gragh &Two = Gragh::Get_instance () ; Two.display () ; std::cout << "Two 的地址是 : " << &Two << std::endl ; // Gragh Three = One ; // 赋值函数是 private, 错误 // Gragh Four ( One ) ; // 拷贝构造函数是 private, 错误 return 0 ; }
2018.5.9更:转移构造函数最好也设置为不可用
private: Gragh ( Gragh&& One ) ; // 或者 Gragh ( Gragh&& One ) = delete ;
(4). 利用 C++11 的新特性 = delete 声明
C++11 添加的 =delete 声明,意味着声明了该函数,但是该函数不可以使用!这样就很好地实现了单例模式,无论是构造函数,还是拷贝构造函数,赋值......都可以加上 = delete 声明,禁止使用。
这样一来,单例模式就只能通过 ::Get_instance 来获取这个类的对象,而且有且仅有一个.
#include <bits/stdc++.h> class Gragh { private: int data ; // 饿汉式, 线程安全 static Gragh null ; public: void display () const { std::cout << "data = " << data << std::endl ; } // error: cannot call member function without object // 要加 static ;非静态成员函数必须要有对象才能调用 static Gragh &Get_instance () { return null ; } public: // 声明为 delete 禁止使用 // 即使是友元, 声明为 public 也不可以使用 Gragh ( Gragh & ) = delete ; Gragh ( const Gragh & ) = delete ; Gragh & operator = (const Gragh & ) = delete ; private: // 构造函数声明成私有的 explicit Gragh () : data ( 100 ) { std::cout << "null 的构造函数被调用" << std::endl ; } } ; // undefined reference to null // 不在类外声明, 会报错 Gragh Gragh::null ; int main () { Gragh &One = Gragh::Get_instance () ; // 只能用 classname:: 因为现在还没有对象 One.display () ; std::cout << "One 的地址是 : " << &One << std::endl ; Gragh &Two = Gragh::Get_instance () ; Two.display () ; std::cout << "Two 的地址是 : " << &Two << std::endl ; // Gragh Three = One ; // 赋值函数不可使用 // Gragh Four ( One ) ; // 拷贝构造函数不可使用 return 0 ; }而且,= delete 声明是针对所有函数,不仅仅是成员函数。只需声明,不需要写函数体。
有的时候,要禁止一些非法参数的调用,比如说, 禁止无参构造函数
Gragh () = delete ;或者是防止其他参数的干扰,都可以设置成 = delete ;
C++11 还增加了 = default ; 声明,经常用在构造函数和析构函数,意味着要求编译器生成一个构造函数或者析构函数。同样不需要写函数体。
(5). 继承自一个类,禁止拷贝,构造,赋值,以及友元访问。
#include <bits/stdc++.h> class father { protected: father () = default ; ~father () = default ; private: father ( father & ) {} father ( const father & ) {} father & operator = ( const father & ) { return *this ; } } ; class Gragh : public father { // 什么继承都会报错, 如果拷贝, 赋值...... private: int data ; // 饿汉式, 线程安全 static Gragh null ; public: void display () const { std::cout << "data = " << data << std::endl ; } // error: cannot call member function without object // 要加 static ;非静态成员函数必须要有对象才能调用 static Gragh &Get_instance () { return null ; } public: // 构造函数声明成公有的 explicit Gragh () : data ( 100 ) { std::cout << "null 的构造函数被调用" << std::endl ; } } ; // undefined reference to null // 不在类外声明, 会报错 Gragh Gragh::null ; int main () { Gragh &One = Gragh::Get_instance () ; // 只能用 classname:: 因为现在还没有对象 One.display () ; std::cout << "One 的地址是 : " << &One << std::endl ; Gragh &Two = Gragh::Get_instance () ; Two.display () ; std::cout << "Two 的地址是 : " << &Two << std::endl ; // Gragh Three = One ; // 赋值函数在父类是私有的 // Gragh Four ( One ) ; // 拷贝构造函数在父类是私有的 return 0 ; }可以看出,虽然子类不能继承基类的 private 函数,但是,子类的拷贝构造和赋值,都需要先调用基类的拷贝构造函数或者赋值。
我还是喜欢用指针,因为每次传引用如果少写了 & , 就容易调用赋值,或者拷贝构造函数。
用指针更方便,不用担心写成赋值或者拷贝 但是容易出错。
#include <bits/stdc++.h> class Gragh { private: int data ; static Gragh *null ; private: explicit Gragh () : data ( 100 ) {} public: void display () const { std::cout << "data = " << data << std::endl ; } static Gragh* const Get_instance () { return null ; } static void End_OK () { // 设置成静态的, 因为不需要对象也可以调用 if ( null ) delete null ; null = NULL ; } ~Gragh () {} } ; Gragh* Gragh::null = new Gragh () ; int main () { Gragh *One = Gragh::Get_instance () ; One->display () ; std::cout << "One 的内容是 : " << One << std::endl ; Gragh *Two = Gragh::Get_instance () ; Two->display () ; std::cout << "Two 的内容是 : " << One << std::endl ; Gragh::End_OK () ; // 记得释放指针的内存 return 0 ; }
运行结果 :
2018.5.9
还可以建立模板,专门产生单例,如果有多个单例,优势很大。但是缺点就是,原先的对象和模板的对象,都可以产生模板的单例,要保证只用其中一个的构造函数。
#include <bits/stdc++.h> #define rep( i , j , n ) for ( int i = int(j) ; i < int(n) ; ++i ) #define dew( i , j , n ) for ( int i = int(n-1) ; i > int(j) ; --i ) #define _PATH __FILE__ , __LINE__ typedef std::pair < int , int > P ; using std::cin ; using std::cout ; using std::endl ; using std::string ; class Gragh { public: int data ; int old ; Gragh() : data ( 100 ) , old ( 0 ) {} ~Gragh () { cout << endl << "析构函数" << endl ; } void test_with_member () { cout << endl << "我是用了数据成员的普通成员函数" << endl ; cout << "data = " << data << endl ; } void test_no_member () { cout << endl << "我是没用数据成员普通的成员函数" << endl ; } static void test_static () { cout << endl << "我是 static 函数" << endl ; } } ; template< typename T > class Single { // 懒汉式单例模板 private: static T* null ; static std::once_flag flag ; private: explicit Single () = delete ; Single ( Single& ) = delete ; Single ( const Single& ) = delete ; Single ( Single&& ) = delete ; Single& operator = ( const Single& ) = delete ; Single& operator = ( const Single&& ) = delete ; public: static T* Get_instance () { std::call_once ( flag , [&] () { null = new T () ; } ) ; return null ; } static bool End_OK () { if ( null ) { delete null ; null = nullptr ; return true ; } return false ; } } ; template< typename T > T* Single<T>::null = nullptr ; template< typename T > std::once_flag Single<T>::flag ; template< typename T > class Single2 { // 饿汉式单例模板 private: static T null ; private: explicit Single2 () = delete ; Single2 ( Single2& ) = delete ; Single2 ( const Single2& ) = delete ; Single2 ( Single2&& ) = delete ; Single2& operator = ( const Single2& ) = delete ; Single2& operator = ( const Single2&& ) = delete ; public: static T* Get_instance () { return &null ; } } ; template< typename T > T Single2<T>::null ; int main () { Gragh *One = Single2<Gragh>::Get_instance () ; cout << One << endl ; Gragh *Two = Single2<Gragh>::Get_instance () ; cout << Two << endl ; return 0 ; }