//单例模板
template <typename T>
class Singleton
{
//使用默认构造和析构函数
Singleton() = default;
~Singleton() = default;
public:
//删除默认的移动、拷贝、赋值、取址
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton& operator=(Singleton &&) = delete;
T *operator &() = delete;
static T* instance()
{
static T object;
return &object;
}
};
使用示例:
//测试类
class Test
{
int id;
public:
void setId(int id)
{
this->id = id;
}
int getId() const
{
return this->id;
}
};
int main()
{
Singleton<Test>::instance()->setId(5);
std::cout << Singleton<Test>::instance()->getId() << std::endl;
}
如果使用的时候觉得调用太长,可以将其 #define 一下,如:
#define SingletonTest Singleton<Test>::instance()
int main()
{
SingletonTest->setId(5);
std::cout << SingletonTest->getId() << std::endl;
}
到这里你可能会提出质疑,我们在此处并没有做到禁止创建 Test 用户类的对象,如果你有这样的需求,那请往下看
为了禁止擅自创建 Test(用户类对象),我们改变下策略:
放开模板限制,在此不再删除取址操作(会影响返回对象的引用),取消模板类构造函数的私有化,作为基类,析构函数标记为virtual;
//单例模式基类,CRTP模板
template <typename T>
class Singleton
{
public:
Singleton() = default;
virtual ~Singleton() = default;
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton& operator=(Singleton &&) = delete;
static T* instance()
{
static T object;
return &object;
}
};
用户类继承单例模板
与之前不同的是,将创建对象的限制放在了用户类(即将构造函数设置为私有),构造函数私有化后,单例模板也无法创建对象,于是,将单例模板作为友元类, 模板类才能访问到用户类的构造函数
class Test : public Singleton<Test>
{
public:
void setId(int id)
{
this->id = id;
}
int getId() const
{
return this->id;
}
private:
int id;
Test() = default;
//单例模板作为友元类
friend class Singleton<Test>;
};
使用示例:
int main()
{
//Test t; //错误,禁止创建对象
Test::instance()->setId(5);
std::cout << Test::instance()->getId();
}
如果这里的 Test类构造函数带有参数,那么应该怎么做?
我们可以考虑将 instance()
函数,写成模板函数,
//单例模式基类,CRTP模板
template <typename T>
class Singleton
{
public:
Singleton() = default;
virtual ~Singleton() = default;
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton& operator=(Singleton &&) = delete;
template <typename ...Args>
static T* instance(Args&&... args)
{
static T object(std::forward<Args>(args)...);
return &object;
}
};
那么需要给Test
类的构造函数传入一个参数,
private:
Test(QString args)
{
qDebug() << args;
}
使用:
Singleton<Test>::instance("1111")->triggle();
Singleton<Test>::instance("2222")->triggle();
//在这里其实可以看到, 下面一行的参数其实是没有传入 Test类的构造函数的, 两次拿到的Test对象是同一个
但是如此一来的话,每次使用Test
单例的时候,就需要传递参数,而我希望仅仅在第一次创建Test
类对象时传递参数,在后续使用时不需要传递参数,那么又该怎样做呢?
当然,我们可以定义一个宏,然后在其他所有需要使用这个Test
单例对象的地方用这个宏定义即可;
#define SingletonTest Singleton<Test>::instance("1111")
也可以从代码上解决这个问题:
我们可以再将Singleton
模板类稍作改动,增加一个 非模板函数类型的instance()
重载函数,来解决每次调用都需要传参数的问题;
template <typename T>
class Singleton
{
public:
Singleton() = default;
virtual ~Singleton() = default;
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton& operator=(Singleton &&) = delete;
template <typename ...Args>
static T* instance(Args&&... args)
{
if (m_ins== nullptr)
{
static std::mutex mu;
std::lock_guard<std::mutex> lock(mu);
if (m_ins== nullptr)
{
m_ins = new T(std::forward<Args>(args)...);
}
}
return m_ins;
}
// 重载函数,无需参数
static T* instance()
{
return m_ins;
}
private:
static T* m_ins;
};
template <typename T>
T* Singleton<T>::m_ins= nullptr;
由于增加了重载instance();
函数,所以之前的instance();
模板函数无法再返回一个局部静态变量的形式创建类对象了,而是换成了懒汉模式单例
;在重载的instance()
;函数中直接将创建好的指针返回即可;
使用示例:
class Test : public Singleton<Test>
{
public:
void testFunc()
{
qDebug() << this << m_a << "test func" << QThread::currentThreadId();
}
protected:
Test(int a) : m_a(a)
{
qDebug() << "construct test object:" << this << a << QThread::currentThreadId();
}
int m_a = 0;
friend class Singleton<Test>;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main thread:" << QThread::currentThreadId();
Singleton<Test>::instance(10)->testFunc(); //首次,传参数构造对象
std::vector<std::thread> vec(10);
for (auto& th : vec)
{
th = std::thread([]()
{
Singleton<Test>::instance()->testFunc(); //无需再传递构造参数
});
}
for (auto& th : vec)
{
if (th.joinable())
th.join();
}
return a.exec();
}
执行结果:
main thread: 0x25a0
construct test object: 0x252a924ec90 10 0x25a0
0x252a924ec90 10 test func 0x25a0
0x252a924ec90 10 test func 0x33cc
0x252a924ec90 10 test func 0x3fac
0x252a924ec90 10 test func 0x3868
0x252a924ec90 10 test func 0x230c
0x252a924ec90 10 test func 0x2528
0x252a924ec90 10 test func 0x1dac
0x252a924ec90 10 test func 0x3514
0x252a924ec90 10 test func 0x3224
0x252a924ec90 10 test func 0x3f50
0x252a924ec90 10 test func 0x18e0
可以看到,真正的类对象是在主线程中,并且构造参数为 10, 而在每个子线程中都可以访问到的都是同一个对象,并且也无需传递instance()
函数参数
如果使用的是Scott Meyers单例模式(局部静态变量)
,那么进程结束时,会主动帮我们调用单例对象的析构函数; 而使用懒汉模式单例
则不会, 且还有其他问题:
1.多线程环境下,双检查锁与CPU重新排序
2.没有手动调用单例对象的析构函数,释放其管理的资源
可以参考下一篇解决方案:跳转