CRTP 单一实例不完整类型或非文本类型

CRTP Singleton Incomplete type or Non-literal type

本文关键字:类型 文本 单一 实例 CRTP      更新时间:2023-10-16

我正在尝试制作一个CRTP单例。这里已经有几个例子了。我不确定我的有什么不同,或者为什么它无法编译。第一次尝试:

template<class Impl>
class Base
{
public:
static const Impl& getInstance();
static int foo(int x);
private:
static const Impl impl{};
};
template<class Impl> inline
const Impl& Base<Impl>::getInstance()
{
return impl;
}
template<class Impl> inline
int Base<Impl>::foo(int x)
{
return impl.foo_impl(x);
}
class Derived1 : public Base<Derived1>
{
public:
int foo_impl(int x) const;
};
int Derived1::foo_impl(int x) const
{
return x + 3;
}
int main(int argc, char** argv)
{
const Derived1& d = Derived1::getInstance();
std::cout << Derived1::foo(3) << std::endl;
return 0;
}

G++ 7.4.0 告诉我:error: in-class initialization of static data member ‘const Derived1 Base<Derived1>::impl’ of incomplete type.

井。那好吧。不知道为什么该类型不完整。尝试:

. . .
private:
static constexpr Impl impl{};
};

现在我们在链接时失败:undefined reference to 'Base<Derived1>::impl'真?!看起来对我来说是定义和初始化的...但即使它确实链接了我有一个带有非平凡析构函数的派生,所以编译器将在编译时轰炸抱怨 constexpr 中使用的非文字类型。

为什么派生 1 不完整?我该如何构建它?

不完整的类型错误来自您在getInstance存在之前在中使用impl的事实。

解决此问题的一种方法是在类定义之外初始化impl并确保在使用之前对其进行初始化:

template <class Impl>
const Impl Base<Impl>::impl {};

尝试以这种方式实现getInstance函数:

template <class Impl>
inline const Impl& Base<Impl>::getInstance() {
static const Impl impl{};
return impl;
}

然后在foo函数中

template <class Impl>
inline int Base<Impl>::foo(int x) {
return getInstance().foo_impl(x);
}

演示

Base<Derived1>被实例化的时间点(就在Derived1定义的开头(,类Derived1是不完整的,因为它直到它的声明结束。CRTP 中确实不可能有一个完整的类型,因为在声明其继承之前,派生类型永远不会完整。

对于非静态数据成员,唯一的解决方法是使用某种指向不完整类型的指针(很可能是std::unique_ptr(。对于静态成员,这也有效,但也可以拆分静态成员的声明和定义。所以而不是

template<Impl>
struct Base {
static Impl impl{};
};

template<Impl>
struct Base {
static Impl impl;
};

并像这样定义它

template<Impl>
static Base<Impl>::impl ={};

Derived1完成后。(请注意,我不确定这对私有静态成员是如何工作的(。在我看来,如果每个实现都为自己执行此操作,即在Derived1完成后添加

template<>
static Base<Derived1>::impl = {};

否则,我认为为多个实现获得正确的顺序将很棘手。