CRTP Singleton 不完整类型或非文字类型

CRTP Singleton Incomplete type or Non-literal type

我正在尝试制作 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{};
 };

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

为什么 Derived1 不完整?我该如何构建它?

不完整的类型错误是因为您在 getInstance 中使用 impl 之前它存在。

解决此问题的一种方法是在 class 定义之外初始化 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);
}

Demo

到实例化 Base<Derived1> 的时间点(就在 Derived1 定义的开始处),class 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 = {};

我认为,否则为多个实现正确排序将很棘手。