我们想要一个类,在整个程序运行期间,它只能被创建出一个对象实例。
为什么需要?比如:配置管理器、日志输出器、线程池------如果出现多个实例,可能导致数据冲突、资源浪费,或者违反业务逻辑(比如一个进程只能有一个打印机控制对象)。
那么问题来了:C++ 中,怎样才能强行限制一个类只能有一个实例?
第一步:禁止外部随意创建对象
正常情况下,你可以写:
cpp
class MyClass {
public:
MyClass() {} // 公有构造函数
};
MyClass a; // 可以创建
MyClass b; // 又可以创建
要阻止这种情况,必须让外界无法调用构造函数 。把构造函数声明为 private 即可:
cpp
class Singleton {
private:
Singleton() {} // 私有构造,外部不能 new
};
现在外部写 Singleton s; 会编译错误。
但光这样还不够,这个类连一个对象都造不出来了,我们要的只是唯一一个 ,不是零个。所以需要提供一个公开的接口来获取那唯一的一个实例。
第二步:提供一个全局访问点
我们需要一个静态成员函数 ,用它来返回唯一的实例。同时需要保存这个唯一实例的指针(或对象)。
保存的地方也只能是类的静态成员,因为只有静态成员属于类本身,而非某个对象。
cpp
class Singleton {
private:
Singleton() {}
static Singleton* instance_; // 静态指针,存放唯一实例的地址
public:
static Singleton* getInstance() {
if (instance_ == nullptr) {
instance_ = new Singleton();
}
return instance_;
}
};
// 静态成员在类外初始化
Singleton* Singleton::instance_ = nullptr;
用法:
cpp
Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
// p1 和 p2 指向同一个对象
此时我们实现了"懒汉式"单例:第一次调用 getInstance() 时才创建实例。
第三步:杜绝拷贝和赋值
即使构造函数私有,外部仍然可能通过拷贝构造或赋值来制造"看起来像第二个实例"的对象:
cpp
Singleton* p1 = Singleton::getInstance();
Singleton s2 = *p1; // 拷贝构造(如果编译器默认生成公有拷贝构造)
为了防止这种情况,需要禁止拷贝构造和赋值操作 。C++98 经典做法是把它们声明为私有且不定义;C++11 及以后可以用 = delete。
cpp
class Singleton {
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// ...
};
现在任何企图拷贝或赋值的代码都会编译报错,保证真的只有一个实例。
第四步:线程安全(多线程环境)
上面的懒汉式 if (instance_ == nullptr) 在多线程下不安全:两个线程同时进入判断,都可能发现指针为空,然后各自 new,破坏单例。
加锁可以解决,但 C++11 之后有一个更优雅、零开销的线程安全单例写法 ------ Meyers Singleton ,它利用函数内静态局部变量的特性。
C++11 标准保证:函数内的静态局部变量在第一次控制流经过其声明时初始化,且初始化是线程安全的。
cpp
class Singleton {
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
static Singleton instance; // 第一次调用时才构造,并且线程安全
return instance;
}
};
用法:
cpp
Singleton& s = Singleton::getInstance();
这里返回的是引用而非指针,更加简洁,也不需要手动 delete。在程序结束时,静态局部变量会自动销毁,生命周期管理由编译器负责。
完整代码示例
cpp
#include <iostream>
class Singleton {
private:
Singleton() {
std::cout << "Singleton created" << std::endl;
}
~Singleton() {
std::cout << "Singleton destroyed" << std::endl;
}
public:
// 禁用拷贝和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
void doSomething() const {
std::cout << "Doing something..." << std::endl;
}
};
int main() {
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
// s1 和 s2 引用的是同一个对象
s1.doSomething();
return 0;
}
输出:
cpp
Singleton created
Doing something...
Singleton destroyed
| 需求 | 对应的技术措施 |
|---|---|
| 只有一个实例 | 私有构造函数,删除拷贝/赋值操作 |
| 提供全局访问点 | 静态成员函数 getInstance |
| 控制实例创建时机 | 懒汉式(首次使用时创建)或饿汉式(预先创建) |
| 多线程环境下保证唯一性 | C++11 静态局部变量(Meyers Singleton) |
| 自动销毁实例 | 静态局部变量的 RAII 特性 |