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


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

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》 p588 有相关描述
image

C++中模板类的静态成员变量,只有在其使用时,才会进行构造
我们把上述代码中 InitPtr 的两行取消注释, 我们发现, 在程序的一开始就会输出 “Class Static Variable”, 说明这个静态变量构造成功了。

如此一来,还是要在 InitPtr 函数中增加与单例对象无关的代码,为了避免发生内存泄漏,仍采用第一种写法。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/288197.html

(0)
上一篇 2022年9月8日
下一篇 2022年9月8日

相关推荐

发表回复

登录后才能评论