C++单例模式的实现

单例模式就是在整个程序运行期都只有一个实例。在代码实现方面,我们要限制new出多于一个对象这种情况的发生。而不是仅仅依靠无保障的约定。

目前大多数的编程语言的做法都是私有化构造函数,对外提供一个获取实例的接口。这样做的目的使实例的创建不能在类外部完成,这样我们只需要在内部保障实例只创建一次即可。我们用一个例子来说明C++的单例:

cpp 复制代码
#include <iostream>
using namespace std;

class TestCls{
        private:
                TestCls(){} // 构造函数私有化,在外部就无法通过new运算符创建新实例
                // 声明单例里的指针,static的成员在这里只是声明,它的初始化还没有完成
                static TestCls *m_instance;
        public:
                ~TestCls(){//析构函数
                        cout << "TestCls is destroying" << endl;
                }
                // 对外暴露一个接口获得单例
                static TestCls* getinstance(){
                        if(m_instance == NULL){
                        		// new出来的对象,一定要用delete运算符删除对应的指针
                                m_instance = new TestCls();
                                // 巧妙的地方
                                static MyGC mygc;
                        }

                        return m_instance;
                }

		
                void test(){
                        cout << "test" << endl;
                }

		// 在单例里再定义一个内部类
        class MyGC {
                public:
                ~MyGC(){//在内部类的析构函数里delete掉单例
                        if(TestCls::m_instance){
                                delete TestCls::m_instance;
                                TestCls::m_instance = NULL;
                        }
                }
        };
};

// static 成员的初始化
TestCls * TestCls::m_instance = NULL;

int main(){
		//获取单例
        TestCls * ptr = TestCls::getinstance();
        cout << ptr << endl;
        ptr = TestCls::getinstance();
        cout << ptr <<endl;
        return 0;
}

定义单例基本上来说是很简单的,就是先私有化构造函数,单例内部new出这个实例,并对外提供获取实例的接口。在这里我想特别分享一下,为什么要弄一个内部类的作用。

目的只有一个就是当程序退出时主动delete掉实例。其次new出来的指针要主动delete掉,malloc出来的东西要主动free掉,这些是编程的好习惯。

我在new出单例时,初始化一个内部类static MyGC mygc; 带上static关键字后,它的生命周期会一直持续到程序退出,因为这个对象不是new也不是malloc出来的,所有它就不会在堆内存里,它会在静态代码区,而单例是new出来的它就在堆内存里。在程序退出时,就会调用内部类的析构函数,这样我们就可以借此delete掉单例。这是一个很巧妙的使用。

C++单例的增强

前面的代码中我们可以看到每个线程调用getinstance()都会先判断是否空,是的话就new一个新的实例。实际上,我们可以改进一下这段代码,C++11提供了call_once的功能,就是说这个功能可以让某个函数只被调用一次。那么我们可以将创建单例的代码,抽象成一个方法,并使用这个方法只被调用一次,这能给我们带来什么好处呢?

我们上面的代码可能没有解决好在多线程中,这个单例的初始化需要互斥的问题。也就是说可能存在多个线程在同时初始化这个实例。一般的做法就是给初始化的代码块加锁,即:

cpp 复制代码
m_mutex.lock();
if(m_instance == NULL){
	m_instance = new TestCls();
	static MyGC mygc;
}
m_mutex.unlock();

现在用c++11提供的call_once函数功能就可以轻松解决这个初始化需要互斥的问题。调整过的部分:

cpp 复制代码
once_flag gflag;
class TestCls{
        private:
				...
                static void createinstance(){
                        if(m_instance == NULL) {
                                m_instance = new TestCls();
                                static MyGC mygc;
                        }
                }
        public:
				...
                static TestCls* getinstance(){
                        call_once(gflag,createinstance);
                        return m_instance;
                }
                ...
		...
};

我们还可以再提高一些效率:

cpp 复制代码
once_flag gflag;
class TestCls{
        private:
				...
                static void createinstance(){
                        if(m_instance == NULL) {
                                m_instance = new TestCls();
                                static MyGC mygc;
                        }
                }
        public:
				...
                static TestCls* getinstance(){
                	if(m_instance == NULL) { // 进一步提高效率
                        call_once(gflag,createinstance);
                     }
                    return m_instance;
                }
                ...
		...
};
相关推荐
非优秀程序员7 小时前
第04章 邮箱大战,163 与 QQ 的暗战|《互联网产品恩仇录》
java·vue.js·架构
知已难寻8 小时前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
文丑颜不良啊10 小时前
LeetCode2481~2500题目解析
java·leetcode
Flynn Liang11 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
聪明的笨猪猪11 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
愿有所息12 小时前
记一次 ARM64 + JDK8 上 fastjson2 的诡异数据错乱
java
秋饼12 小时前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发
SL_staff13 小时前
JVS-BI 实战:业务人员如何用零代码 ELT + 可视化配置构建实时经营分析闭环
java·spring·全栈
SL_staff14 小时前
企业AI落地难?JVS-AI渐进式实践:安全可控、小切口验证、标准化沉淀与规模化复制
java·开源·aigc
David猪大卫14 小时前
【C++修炼】map 和 set 的使用
c语言·开发语言·数据结构·c++·经验分享·笔记·学习