防止 CRTP 模式在 "pure virtual" 次调用中发生堆栈溢出

Protect CRTP pattern from stack overflowing in "pure virtual" calls

考虑以下标准 CRTP 示例:

#include <iostream>

template<class Derived>
struct Base {
    void f() { static_cast<Derived *>(this)->f(); }
    void g() { static_cast<Derived *>(this)->g(); }
};

struct Foo : public Base<Foo> {
    void f() { std::cout << 42 << std::endl; }
};

int main() {
    Foo foo;
    foo.f(); // just OK
    foo.g(); // this will stack overflow and segfault
}

如果这是常规虚拟继承,我可以将虚拟 fg 方法标记为纯方法

struct Base {
    virtual void f() = 0;
    virtual void g() = 0;
};

并得到关于 Foo 是抽象的编译时错误。但是 CRTP 不提供这种保护。我能以某种方式实现它吗?运行时检查也是可以接受的。我考虑过将 this->f 指针与 static_cast<Derived *>(this)->f 进行比较,但未能成功。

你可以在编译时断言这两个指向成员函数的指针是不同的,例如:

template<class Derived>
struct Base {
    void g() {
        static_assert(&Derived::g != &Base<Derived>::g,
                      "Derived classes must implement g()."); 
        static_cast<Derived *>(this)->g(); 
    }
};

你可以考虑做这样的事情。您可以使 Derived 成为成员,并在每次实例化 Base 时直接将其作为模板参数提供,或者使用 类型别名 ,就像我在这个例子:

template<class Derived>
struct Base {
    void f() { d.f(); }
    void g() { d.g(); }
private:
    Derived d;
};

struct FooImpl {
    void f() { std::cout << 42 << std::endl; }
};

using Foo = Base<FooImpl>;

int main() {
    Foo foo;
    foo.f(); // OK
    foo.g(); // compile time error
}

当然 Derived 不再是 派生的 所以你可以为它取一个更好的名字。

你可以使用这个解决方案,你可以有纯 "non-virtual abstract" 功能,它尽可能映射到 CRTP recommendation of H. Sutter:

template<class Derived>
struct Base
  {
  void f(){static_cast<Derived*>(this)->do_f();}
  void g(){static_cast<Derived*>(this)->do_g();}

  private:
  //Derived must implement do_f
  void do_f()=delete;
  //do_g as a default implementation
  void do_g(){}
  };

struct derived
  :Base<derived>
  {
  friend struct Base<derived>;

  private:
  void do_f(){}
  };

还有一种可能:

#include <iostream>

template<class Derived>
struct Base {
    auto f() { return static_cast<Derived *>(this)->f(); }
    auto g() { return static_cast<Derived *>(this)->g(); }
};

struct Foo : public Base<Foo> {
    void f() { std::cout << 42 << std::endl; }
};

int main() {
    Foo foo;
    foo.f(); // just OK
    foo.g(); // this will not compile
}

对于GCC,它给出了一个非常清晰的错误信息("error: use of 'auto Base::g() [with Derived = Foo]' before deduction of 'auto'"),而对于Clang,它给出了一个可读性稍差的无限递归模板实例Base<Foo>::gg 实例化自身但最终以错误结束。