当需要严格控制一个类在全局只有唯一一个实例,并且可以随时调用它的时候,就可以使用单例模式。
单例模式可以以实例化时间分为懒汉式和饿汉式、以C实现方式分为C++98的实现以及C++11的实现。
懒汉式
c++11
class Singleton {
public:
static Singleton* getInstance() {
if(m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
private:
Singleton(){}
~Singleton(){}
static Signeton* m_instance;
};
class Singleton {
public:
static Singleton& GetInstance() {
static Singleton instance;
return instance;
}
private:
Singleton() = default;
~Singleton() {}
};
也就是说, Singleton 通过将构造函数私有化实现了对实例化的控制,只有通过调用 getInstance() 才能生成并获得这唯一一个实例。
同时,这是一个懒汉式的实现,只有在调用 getInstance() 一次过后才有一个 Singleton 被实例化。
代码注解:
-
这段代码使用指针的目的是为了延迟单例类的实例化,并在需要时动态地创建和访问实例对象。
-
通过使用 new 运算符创建一个 Singleton 类的实例对象,并将其赋值给静态成员 m_instance,以确保只有一个实例对象被创建。需要注意的是,静态成员 m_instance 存储的是实例对象的地址,而不是对象本身。因此,在使用时需要通过解引用操作符 * 来获取实际的对象。
静态成员变量定义只能在源文件:
重复编译 减少初始化操作
静态成员:
-
如果把类的成员声明为静态的,就可以把它与类的对象独立开来(静态成员不属于对象)
-
用 static 关键字把类的成员变量声明为静态,表示它在程序中(不仅是对象)是共享的。
-
静态成员只有一份,在全局存储区
-
静态成员使用类名加范围解析运算符 :: 就可以访问,不需要创建对象。
饿汉式
// in 'Singleton.h'
class Singleton {
public:
static Singleton& GetInstance {
return instance;
}
private:
Singleton() = default;
~Singleton() {}
static Singleton instance;
};
// in 'Singleton.cpp'
Singleton Singleton::instance;
代码讲解
-
GetInstance() 方法返回一个 Singleton 对象的引用。
-
在 C++11 中,使用局部静态变量可以确保只有一个实例对象被创建。在这段代码中,instance 是一个局部静态变量,它在第一次调用 GetInstance() 方法时被创建,而在后续的调用中会直接返回已经存在的实例对象。
-
构造函数 Singleton() 和析构函数 ~Singleton() 都使用 default 关键字进行了默认定义,即使用编译器生成的默认实现。它们被声明为 private,防止在类外部直接创建和销毁对象。
-
通过这种实现方式,可以简洁地实现只有一个实例对象被创建,并且可以通过引用来访问该对象。
懒汉模式和恶汉模式是两种单例模式实现方式的区别:
懒汉模式:在首次使用时才创建实例对象,延迟实例化,可能存在线程安全问题。
恶汉模式:在类加载时就创建实例对象,立即实例化,不存在线程安全问题。
懒汉模式节省内存空间,但可能引入延迟和线程安全问题;恶汉模式简单直接,但可能浪费部分内存空间。选择取决于内存占用、延迟和线程安全需求。
竞争的场景:
两个线程在调用结构的过程中,如判断是否空指针的时候,一个在判断为空指针后准备创建对象,另一个在此时也判断为空的指针
//Singleton.h
/* C++98 */
class Singleton11{
public:
static Singleton98* getInstance() {
if(m_instance == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
if(m_instance == nullptr) {
m_instance = new Singleton();
}
}
return m_instance;
}
private:
//...
}
/* C++11 */
class Singleton11{
public:
static Singleton11& getInstance() {
std::call_once(m_flag, []() {
m_instance.reset(new Singleton11);
});
}
private:
//...
static std::mutex m_mutex;
}
//Singleton.cpp
/* C++98 */
Singleton98* Singleton98::m_instance = nullptr;
std::mutex Singleton98::m_mutex;
/* C++11 */
std::unique_ptr<Singleton11> Singleton11::m_instance;
std::once_flag Singleton11::m_flag;
问题:
-
多线程需要考虑多次new的问题,需要使用线程同步如互斥锁
-
忘记delete!
用处
资源共享:当多个对象需要共享同一个资源时,可以使用单例模式来管理该资源。例如,数据库连接池、线程池等。
配置信息:单例模式可以用于管理全局的配置信息,确保在整个应用程序中只有一个配置对象,方便访问和修改配置。
日志记录器:在日志记录中,使用单例模式可以确保只有一个日志记录器实例,所有的日志信息都在同一个地方进行记录,并且可以方便地访问和控制日志记录器。