设计模式 学习笔记(1) 单例模式之饿汉式

单例模式

单例模式,就是指一个类有且仅有一个对象实例。

例如做一个学生成绩管理系统,我定义了一个“系统类”,用于存储数据,类似于“数据库”的功能。这个系统类当然只能有一个对象实例了;如果有多个实例,就会导致数据不同步。

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 ;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值