单例模式
单例模式,就是指一个类有且仅有一个对象实例。
例如做一个学生成绩管理系统,我定义了一个“系统类”,用于存储数据,类似于“数据库”的功能。这个系统类当然只能有一个对象实例了;如果有多个实例,就会导致数据不同步。
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 ;
}
有一种很流行的方式, #define 宏定义禁止类的构造,拷贝构造函数,赋值操作等等。
有的公司还以此为编码规范
#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 ;
}
2018.8.2 更新
控制单例类不被继承,或者单例类的派生对象不能有实例。
加一把“锁”。参考博客 不能被继承的类
#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 ;
// 加一把锁, 设置 Entity 为友元, 且构造函数设置为 private
// 这样一来, Entity 的派生类不能调用根类 Lock 的构造函数,都不能继承自 Entity
// 效果等同于 final
template< class T >
class Lock {
friend T ;
private:
Lock() = default ;
Lock(const Lock&) = delete ;
Lock& operator=(const Lock&) = delete ;
Lock& operator=(const Lock&&) = delete ;
} ;
// 虚拟继承的原因:派生类会尝试直接调用 Lock 的构造函数
// 如果不是虚拟继承:派生类会调用 Entity 的构造函数,Entity 会调用 Lock 的构造函数
class Entity : virtual private Lock< Entity > {
public:
// getInstance 是单例类的入口
static Entity* getInstance() {
try {
if ( null == nullptr )
throw std::logic_error("静态对象为空") ;
} catch ( std::exception &e ) {
cout << e.what() << endl ;
}
return null ;
}
// 释放 Entity 单例类的空间
static bool littersGC() {
cout << "main 函数之后清理静态对象" << endl ;
if ( null not_eq nullptr )
delete null ;
null = nullptr ;
return true ;
}
private:
// 构造函数设为 private
explicit Entity() = default ;
~Entity() noexcept = default ;
Entity(const Entity &) = delete ;
Entity& operator=(const Entity &) = delete ;
Entity& operator=(const Entity &&) = delete ;
private:
static Entity *null ;
} ;
Entity* Entity::null = new Entity ;
// 尝试继承 Entity
class child : public Entity {} ;
int main () {
constexpr int N = 10 ;
Entity *Instance[N] ;
rep ( i , 0 , N )
Instance[i] = Entity::getInstance() ;
rep ( i , 0 , N )
cout << "Instance[ " << i << " ] = " << Instance[i] << endl ;
Entity::littersGC() ;
// child One ; // 编译错误, Entity 的子类不能有实例
return 0 ;
}
程序结果:
其实,C++11 已经提供了一种禁止对象被继承的方式:final
class Entity final {} ;
2018.8.2 更新
单例类和 RAII 的结合。
RAII 的本质就是通过对象控制资源的初始化和释放,优点是不用显式释放资源,对象所需的资源在其生命周期内始终有效。
#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 ;
// 加一把锁, 设置 Entity 为友元, 且构造函数设置为 private
// 这样一来, Entity 的派生类不能调用根类 Lock 的构造函数,都不能继承自 Entity
// 效果等同于 final
template< class T >
class Lock {
friend T ;
private:
Lock() = default ;
Lock(const Lock&) = delete ;
Lock& operator=(const Lock&) = delete ;
Lock& operator=(const Lock&&) = delete ;
} ;
// 虚拟继承的原因:派生类会尝试直接调用 Lock 的构造函数
// 如果不是虚拟继承:派生类会调用 Entity 的构造函数,Entity 会调用 Lock 的构造函数
class Entity : virtual private Lock< Entity > {
public:
// getInstance 是单例类的入口
static Entity* getInstance() {
try {
if ( null == nullptr )
throw std::logic_error("静态对象为空") ;
} catch ( std::exception &e ) {
cout << e.what() << endl ;
}
return null ;
}
private:
// 构造函数设为 private
explicit Entity() = default ;
~Entity() noexcept = default ;
Entity(const Entity &) = delete ;
Entity& operator=(const Entity &) = delete ;
Entity& operator=(const Entity &&) = delete ;
// 利用内部类可访问外部类的 static 变量的特点
class Litter {
public:
~Litter() {
cout << "销毁单例类 null" << endl ;
if ( null not_eq nullptr )
delete null ;
null = nullptr ;
}
} ;
private:
static Entity *null ;
// 声明一个 Litter 静态对象, 程序结束后会调用 Litter 的析构函数
static Litter litter ;
} ;
Entity* Entity::null = new Entity ;
Entity::Litter Entity::litter ;
// 尝试继承 Entity
class child : public Entity {} ;
int main () {
constexpr int N = 10 ;
Entity *Instance[N] ;
rep ( i , 0 , N )
Instance[i] = Entity::getInstance() ;
rep ( i , 0 , N )
cout << "Instance[ " << i << " ] = " << Instance[i] << endl ;
// child One ; // 编译错误, Entity 的子类不能有实例
rep ( i , 0 , N )
Instance[i] = nullptr ;
return 0 ;
}