我们有如下的单例设计模式的实现:
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 有相关描述
C++中模板类的静态成员变量,只有在其使用时,才会进行构造
我们把上述代码中 InitPtr 的两行取消注释, 我们发现, 在程序的一开始就会输出 “Class Static Variable”, 说明这个静态变量构造成功了。
如此一来,还是要在 InitPtr 函数中增加与单例对象无关的代码,为了避免发生内存泄漏,仍采用第一种写法。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/288197.html