简介
今天在写单例模式时,计划使用隐藏类的析构来释放内存,之前一直采用的注册方式(atexit)。但是在使用valgrind检测内存泄露时发现出现了error,之前使用注册方式从未出现。于是在隐藏类的析构加了printf,发现未输出,于是在构造又加了printf,发现也未输出。可想这个类没有发生构造,但是相比于其他静态变量,其他静态变量都是正常的,唯一的不同点在于这个隐藏类变量从未使用过。
代码
#ifndef __SINGLETON_H__
#define __SINGLETON_H__
#include "mutex.h"
#include "exception.h"
#include "refcount.h"
#include <bits/move.h>
namespace eular {
template<typename T>
class Singleton {
public:
struct SObject
{
private:
friend class Singleton<T>;
SObject(T *obj, RefCount *ref) :
mObj(obj),
mRefPtr(ref)
{
assert(mRefPtr != nullptr);
mRefPtr->ref();
}
public:
SObject(const SObject &other)
{
mObj = other.mObj;
mRefPtr = other.mRefPtr;
mRefPtr->ref();
}
SObject &operator=(const SObject &other)
{
mObj = other.mObj;
mRefPtr = other.mRefPtr;
mRefPtr->ref();
return *this;
}
~SObject()
{
mObj = nullptr;
mRefPtr->deref();
mRefPtr = nullptr;
}
T *operator->()
{
if (mObj == nullptr) {
throw Exception("nullptr object");
}
return mObj;
}
T &operator*()
{
if (mObj == nullptr) {
throw Exception("nullptr object");
}
return *mObj;
}
operator T*()
{
return mObj;
}
operator const T*() const
{
return mObj;
}
private:
T *mObj;
RefCount *mRefPtr;
};
template<typename... Args>
static SObject get(Args... args)
{
// 编译期间检测类型完整性
static_assert(sizeof(T), "incomplete type");
AutoLock<Mutex> lock(mMutex);
if (mInstance == nullptr) {
mInstance = new T(std::forward<Args>(args)...);
(void)mDeleter; // 模板静态成员变量需要使用才会构造
// ::atexit(Singleton<T>::free); // 在mian结束后调用free函数
}
SObject obj(mInstance, &mRef);
return obj;
}
/**
* @brief 重置实例, 会返回一个新的地址,所以原来的会失效,对于单例模式,此方法用的不太多
*/
template<typename... Args>
static SObject reset(Args... args)
{
AutoLock<Mutex> lock(mMutex);
if (mRef.load() == 0) {
if (mInstance != nullptr) {
delete mInstance;
mInstance = nullptr;
}
mInstance = new T(std::forward<Args>(args)...);
}
SObject obj(mInstance, &mRef);
return obj;
}
static void free()
{
if (mRef.load() > 0) {
return;
}
AutoLock<Mutex> lock(mMutex);
if (mInstance != nullptr) {
delete mInstance;
mInstance = nullptr;
}
}
private:
class Deleter {
public:
~Deleter()
{
Singleton<T>::free();
}
};
private:
static T *mInstance;
static RefCount mRef;
static Mutex mMutex;
static Deleter mDeleter;
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
template<typename T>
T *Singleton<T>::mInstance = nullptr;
template<typename T>
Mutex Singleton<T>::mMutex;
template<typename T>
RefCount Singleton<T>::mRef;
template<typename T>
typename Singleton<T>::Deleter Singleton<T>::mDeleter;
} // namespace eular
#endif // __SINGLETON_H__
测试代码
#include <stdio.h>
template <class T>
class StaticObject {
public:
class None {
public:
None() { printf("%s()\n", __func__); }
~None() { printf("%s()\n", __func__); }
};
static None none;
};
template<class T>
typename StaticObject<T>::None StaticObject<T>::none;
// static StaticObject<int>::None none; // 1
int main(int argc, char **argv)
{
StaticObject<int> a;
// a.none; // 2
return 0;
}
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
g++ main.cc -o main -std=c++11 -g -O0
放开1处代码,注释2处代码
或注释1,放开2
均可正常看到输出
同时注释1、2处代码
无法看到输出
同时放开两处代码
则会输出两次
由此可见,模板类的静态成员变量需要使用才会构造
GDB
下面是放开a.none
的调试
静态成员变量的初始化在__libc_start_main
之后,main
之前。由此也可以确定,静态成员变量的创建发生在运行期间
后话
将同样的测试代码放在vs2019中同样会不输出,所以这应该是标准行为。