//单例模式基类,CRTP模板
template <typename T>
class Singleton
{
protected:
Singleton() = default;
virtual ~Singleton() = default;
public:
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton& operator=(Singleton &&) = delete;
static T* instance()
{
static T object;
return &object;
}
};
用户类继承单例模板,构造函数私有化后,单例模板也无法创建Test
对象,于是,将单例模板作为友元类, 模板类才能访问到用户类的构造函数
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>;
};
使用示例:
//如果觉得 Test::instance() 写法太长,也可以如下宏定义以下
#define SingletonTest Singleton<Test>::instance()
int main()
{
//Test t; //错误,禁止创建对象
Test::instance()->setId(5);
std::cout << SingletonTest->getId();
}
如果这里的 Test类构造函数带有参数,那么应该怎么做?
我们可以考虑将 instance()
函数,写成可变模板函数,
//单例模式基类,CRTP模板
template <typename T>
class Singleton
{
protected:
Singleton() = default;
virtual ~Singleton() = default;
public:
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
{
protected:
Singleton() = default;
virtual ~Singleton() = default;
public:
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()
函数参数
这个类还有如下问题:
1.如果使用 Scott Meyers 式的单例模式,那么程序结束时候,系统会自动帮我们调用此单例类的析构函数,即使此析构函数是私有的,也可以正常调用到
2.如果使用懒汉模式的单例,那么程序结束时,不会主动帮我们调用单例类的析构函数,需要手动调用一个Release或者Destory的函数,来释放此单例类对象(千万不能在单例类的析构函数中 delete,否则会引起递归调用,导致程序崩溃)
3.为了解决第二点忘记手动释放的问题,可以使用智能指针 shared_ptr
或者 unique_ptr
来管理此单例类,这两种智能指针都需要一个公有的析构函数,且不能使用 make_shared
或者 make_unique
来创建单例类对象,因为这两个函数都需要公有的构造函数,这违反了单例模式的原则,所以只能使用 .reset(new T(args)...)
的形式来创建单例对象
4.多线程环境下,双检查锁与CPU重新排序。
可以参考下一篇结尾的解决方案:跳转
为了解决前三个问题,可以用如下形式:
template <typename T>
class Singleton
{
protected:
// 模板类的构造函数必须公有
Singleton()
{
qDebug() << "create singleton template";
}
virtual ~Singleton()
{
qDebug() << "destruct singleton template";
}
public:
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)
{
//此处只能使用reset或者其他形式的new,不能使用make_unique,因为make_unique需要公有构造函数
m_ins.reset(new T(std::forward<Args>(args)...));
}
}
return m_ins.get();
}
// 重载函数,无需参数
static T* instance()
{
return m_ins.get();
}
private:
static std::unique_ptr<T> m_ins;
};
template <typename T>
std::unique_ptr<T> Singleton<T>::m_ins= nullptr;
class MyTest : public Singleton<MyTest>
{
public:
void testFunc()
{
qDebug() << this << m_a << "test func" << QThread::currentThreadId();
}
private:
MyTest(int a) : m_a(a)
{
qDebug() << "construct test object:" << this << a << QThread::currentThreadId();
}
public:
// 使用智能指针,析构函数必须公有
~MyTest()
{
qDebug() << "destruct test object:";
}
private:
int m_a = 0;
friend class Singleton<MyTest>;
};