欢迎来到军工软件开发人才培养基地——学到牛牛

单例模式

时间:2024-05-06 07:01:10 来源:学到牛牛

单例模式是一种最为常见的软件设计模式。单例模式要求:单例对象所在的类必须保证只能创建一个对象。单例模式在我们日常生活和软件开发中的应用比比皆是,比如:windows系统只有一个任务管理器,一个市只有一个市长。

 

 

如何保证一个类最多只能创建一个对象呢?这个问题不能交由使用者去做处理,比如用全局变量。而应该由这个类的创建者在实现该类的时候考虑问题的解决。

单例模式巧妙的使用C++成员权限,将构造函数和拷贝构造函数隐藏起来(private),从而有效限定使用中对对象的自由创建。然后开放一个(static)接口,通过静态方法创建对象,并在静态方法中限定对象的唯一创建。

单例模式的创建方式一般有两种:懒汉式和饿汉式。

1.懒汉模式

懒汉:顾名思义,不到万不得已该类不会去实例化对象。将对象的示例推迟到需要该对象的时候。

// 单例模式之懒汉模式

class Singleton{

public:

static Singleton* createSingleton(){ // static方法

if( m_handler == nullptr ){

m_handler = new Singleton();

}

return m_handler;

}

private:

Singleton(); //  私有化构造函数

~Singleton(); //  私有化析构函数

Singleton( const Singleton & ); //  私有化拷贝构造函数,防止通过拷贝构造复制对象

static Singleton* m_handler;

};

 

Singleton* Singleton::m_handler = nullptr;

 

int main()

{

Singleton *ptr1 = Singleton::createSingleton();

Singleton *ptr2 = ptr1-> createSingleton();  // ptr1 和 ptr2 指向同一个对象

return 0;

}

2.饿汉模式

饿汉:单例类在创建类的时候就创建了对象。

// 单例模式之饿汉模式

class Singleton{

public:

static Singleton* getSingleton(){

return m_handler;

}

private:

Singleton(); //  私有化构造函数

~Singleton(); //  私有化析构函数

Singleton( const Singleton & ); //  私有化拷贝构造函数,防止通过拷贝构造复制对象

static Singleton* m_handler;

};

Singleton* Singleton::m_handler = new Singleton; // 类创建时,创建对象

 

int main()

{

Singleton *ptr1 = Singleton::createSingleton();

Singleton *ptr2 = ptr1-> createSingleton();  // ptr1 和 ptr2 指向同一个对象

return 0;

}

 

3.单例模式中的线程安全

前面我们考虑了单例模式的懒汉式和饿汉式,但是我们只考虑了普通单线程情况。如果考虑到多线程情况,那么上面的懒汉模式则不是线程安全的。而饿汉模式因为在编译阶段已经创建了对象,所有它是线程安全的。

如何解决懒汉模式的线程不安全呢?通常情况我们可以通过互斥锁解决临界资源的访问问题。

// 单例模式之懒汉模式+线程安全

class Singleton{

public:

static Singleton* createSingleton(){ // static方法

if( m_handler == nullptr ){   // 解决访问效率问题

pthread_mutex_lock( &m_lock );

if( m_handler == nullptr ){

m_handler = new Singleton();

}

pthread_mutex_unlock( &m_lock );

return m_handler;

}

}

private:

Singleton(); //  私有化构造函数

~Singleton(); //  私有化析构函数

Singleton( const Singleton & ); //  私有化拷贝构造函数,防止通过拷贝构造复制对象

static Singleton* m_handler;

static pthread_mutex_t  m_lock;

};

 

Singleton* Singleton::m_handler = nullptr;

pthread_mutex_t  Singleton::m_lock = PTHREAD_MUTEX_INITIALIZER;

 

 

int main()

{

Singleton *ptr1 = Singleton::createSingleton();

Singleton *ptr2 = ptr1-> createSingleton();  // ptr1 和 ptr2 指向同一个对象

return 0;

}

上面例程通过互斥锁,看似解决了多线程中的临界资源互斥问题。但是实际上并非如此。具体问题如下:

上面代码中:m_handler = new Singleton();  我们期望的执行顺序是:

(1)分配一段内存   (2)构造对象,放入内存   (3)m_handler存内存地址

但是实际执行可能是:

(1)分配一段内存   (2) m_handler存内存地址   (3)构造对象,放入内存

那么后面的情况可能导致,对象还没创建,但是已经被另外一个线程拿去使用了,这种情况可能导致严重错误。那么如何解决呢?大家可以思考一下。