一般我们要把一个类写成单例类时,会在类中定义一个static的自己,并将构造函数(甚至析构函数)的访问权限设置为非public,然后提供一个类似GetReference的接口将唯一实例获取出来使用:
// Sgt.h
#pragma once
class Sgt {
public:
static Sgt& GetReference()
{
return instance;
}
protected:
Sgt() = default;
virtual ~Sgt() = default;
private:
static Sgt instance;
};
// Main.cpp
Sgt Sgt::instance;
int main()
{
auto& sgt = Sgt::GetReference();
}
这种方法需要一个cpp去承载类内的静态成员变量的定义,在编译的时候才能正确生成instance的变量实例,否则会报“无法解析的外部命令”的错误(这个定义又不能放到头文件中,否则多个cpp包含该头文件后会出现重复定义),这对于我们打算写一个head-only的库并在库中使用单例模式或是类中打算有静态成员变量来说是十分不友好的。
C++17通过引入Inline Variable(内联变量)
的方式来提供这种支持,我们可以直接在类中声明并定义静态成员变量,这样不需要再找一个cpp文件来放静态成员变量的定义。C++14上还不支持这个功能,但是看到了boost的singleton源码实现后,发现了模板的一个妙用,可以实现近乎类似的效果,在此记录分享一下~
// Singleton.hpp (Author: Deyou Kong, Date: 2021-06-25, MIT License)
#pragma once
// Singleton base class, allow to use C++ CRTP.
template <typename T, int N = 0>
// Allows to specify the value of N to achieve
// a special scenario that there are different
// instances of the same type, breaking the
// singleton limit.
class Singleton {
public:
static T& GetReference()
{
static T instance{};
return instance;
}
T& operator()() const
{
return reference;
}
private:
static T& reference;
};
// If there is a class B that inherits class A,
// and class A inherits from Singleton<A>, and
// then an instance of B is created, then there
// will be two instances of A, one for Singleton<A>
// created in Singleton<A>::GetReference(), and
// one is in the B instance. So the singleton
// class is Singleton<A>, not A.
template <typename T, int N>
T& Singleton<T, N>::reference = Singleton<T, N>::GetReference();
这个Singleton采用C++的奇异递归模板模式(CRTP, Curiously Recurring Template Pattern),假设类C期望实现为一个单例类,一般写成下面的形式。
class C : public Singleton<A> {
...
};
假设想在类D中加一个内联静态变量,可以写成下面的形式。Singleton支持指定一个唯一的ID值用来做不同实例的标识,这样在D类中就能有两个int类型的内联静态变量。
class D {
...
Singleton<int, 1> inlineStaticValue1;
Singleton<int, 2> inlineStaticValue2;
};
enjoy~ 模板的妙用让head-only更简单~