私有构造函数是单例模式成立的前提,它禁止外部直接实例化,确保仅能通过静态工厂方法创建唯一对象,否则单例将失效。

私有构造函数不是“为了单例而设”的装饰,它是强制控制对象生命周期的第一道门。只要构造函数是 private,任何外部代码(包括友元以外的类、函数、模板实例化)就无法用 new、Singleton s; 或 std::make_unique 创建实例——这是单例能成立的前提,也是静态工厂能接管创建逻辑的必要条件。
为什么必须把构造函数设为 private
不设私有,所有单例实现都形同虚设。常见错误是只禁了拷贝,却留着公有构造:
- 用户直接写
Singleton s;就生成新实例,破坏唯一性 -
std::vector可能隐式调用默认构造,编译失败或行为未定义 - 模板代码(如
std::make_shared)会尝试访问公有构造,导致编译报错:() error: calling a private constructor - 即使你写了
getInstance(),也无法阻止别人绕过它——这不是设计模式,是“建议模式”
private 构造函数 + static 成员函数 = 最小可行单例
最简但可用的单例骨架,仅靠语言机制不依赖锁或智能指针:
class Logger {
private:
Logger() { std::cout public:
static Logger& getInstance() {
static Logger instance; // C++11 起线程安全初始化
return instance;
}<pre class="brush:php;toolbar:false;">void log(const char* msg) { std::cout <p>};
</p>
-
static Logger instance是局部静态变量,首次调用getInstance()时才构造,懒加载 - C++11 标准 §6.7 明确保证:多线程同时首次进入该语句,仅一个线程执行构造,其余阻塞等待——无需
std::mutex - 返回引用而非指针,避免意外
delete或空检查;返回指针需额外处理析构逻辑 - 析构在
main()返回后、全局对象销毁阶段自动发生,顺序由定义顺序决定
静态工厂与单例共用 private 构造时的关键区别
静态工厂(如 create())和单例(getInstance())都能用 private 构造,但意图和契约完全不同:
- 单例的
getInstance()必须始终返回**同一对象地址**,多次调用&Logger::getInstance() == &Logger::getInstance()为 true - 静态工厂的
create()通常返回**新对象**(如std::unique_ptr),每次调用地址不同,且生命周期由调用方管理 - 若工厂也想复用单例实例(例如缓存连接),必须显式判断并返回引用/指针——但这已脱离“工厂”本意,容易混淆职责
- 二者不可混用接口:不要让
getInstance()返回std::shared_ptr,这会让人误以为可随意拷贝;也不要让create()返回引用,违背工厂语义
最容易被忽略的析构陷阱
局部静态变量版本看似完美,但析构时机极易被误判:
- 析构发生在
main()返回之后,**早于静态变量的销毁顺序末尾**,但晚于栈对象析构 - 如果其他静态对象(如全局
std::ofstream)在析构时调用Logger::getInstance().log(),会触发未定义行为——因为Logger实例可能已被析构 - 解决方法只有两种:要么确保日志器是“最后析构”的静态对象(难控),要么改用堆上分配 + 手动释放(牺牲懒加载和简洁性)
- 更现实的做法是:接受这个限制,在日志器内部加一个
isDestroyed_标记,log()先检查,避免崩溃但不保证输出
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











