单例以及模板类的静态成员变量的生命周期

我们有如下的单例设计模式的实现:

template <typename T>
class OnceSingle {
public:
    OnceSingle() = delete;
    OnceSingle& operator=(const OnceSingle<T>& m) = delete;
    ~OnceSingle() = default;

    class CGFunctionClass {
        public:
            ~CGFunctionClass() {
                if (m_ptr != nullptr) {
                    delete m_ptr;
                    m_ptr = nullptr;
                }
            }
    };

    static T* getInstance() {
        std::call_once(s_flag, InitPtr);
        return m_ptr;
    }

private:
    static void InitPtr() {
        m_ptr = new T();
        static CGFunctionClass cg;
    }

private:
    static T* m_ptr;
    static std::once_flag s_flag;
};

template<typename T>
T* OnceSingle<T>::m_ptr = nullptr;

template<typename T>
std::once_flag OnceSingle<T>::s_flag;

有如下的单元测试函数:

TEST(MyUtil, test_singleton) {
    int *raw_ptr = OnceSingle<int>::getInstance();
    int *same_raw_ptr = OnceSingle<int>::getInstance();
    EXPECT_EQ(raw_ptr, same_raw_ptr);
    raw_ptr = nullptr;
    same_raw_ptr = nullptr;
}

在这个模板单例类中,存在一个裸指针 m_ptr,当我们的单元测试结束的时候,静态对象 static CGFunctionClass cg会进行析构,从而保证了这个裸指针所指向的对象也能够得到析构,从而避免了内存泄漏。
注意到这个静态CGFunctionClass的对象cg,是与 new T 同时出现的。
那么我们能不能将这个cg对象定义为这个类的静态成员变量呢?这样能够让代码的结构更加清晰。同时也让这个静态变量在析构的时候能够析构这个裸指针。
答案是否定的!
现在我们进行分析,我们有如下代码


template <typename T>
class OnceSingle {
public:
    OnceSingle() = delete;
    OnceSingle& operator=(const OnceSingle<T>& m) = delete;

    ~OnceSingle() = default;
    class CGFunctionClass {
        public:
            ~CGFunctionClass() {
                if (m_ptr != nullptr) {
                    delete m_ptr;
                    m_ptr = nullptr;
                }
            }
    };

    static T* getInstance() {
        std::call_once(s_flag, InitPtr);
        return m_ptr;
    }
private:
    static void InitPtr() {
        // s;
        // cg;
        m_ptr = new T();
    }

private:
    static T* m_ptr;
    static std::once_flag s_flag;
    static CGFunctionClass cg;
    static MyString s;
};

template<typename T>
T* OnceSingle<T>::m_ptr = nullptr;

template<typename T>
std::once_flag OnceSingle<T>::s_flag;

template<typename T>
typename OnceSingle<T>::CGFunctionClass OnceSingle<T>::cg;

template<typename T>
MyString OnceSingle<T>::s("Class Static Variable");

我们对 MyString 这个类型的构造函数和析构函数增加了字符串输出函数,使其在构造和析构的时候能够打印出信息。我们继续运行之前的单元测试,发现这个 s 对象的并没有被构造,更不用说析构了,由此可知 cg 这个变量同时也没有构造和析构,因此上述代码的执行会造成内存泄漏的问题。
我们把 InitPtr 函数中注释掉的两行取消注释,发现 s 对象成功构造了。
查阅《C++ Primer》5th p588 有相关描述
image
C++中模板类的静态成员变量,只有在其使用时,才会进行构造
我们把上述代码中 InitPtr 的两行取消注释, 我们发现, 在程序的一开始就会输出 "Class Static Variable", 说明这个静态变量构造成功了。
如此一来,还是要在 InitPtr 函数中增加与单例对象无关的代码,为了避免发生内存泄漏,仍采用第一种写法。

posted on   LambdaQ  阅读(182)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示