保护CRTP模式免于"纯虚拟"调用中的堆栈溢出

ura*_*nix 21 c++ virtual-functions crtp pure-virtual

请考虑以下标准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
}
Run Code Online (Sandbox Code Playgroud)

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

struct Base {
    virtual void f() = 0;
    virtual void g() = 0;
};
Run Code Online (Sandbox Code Playgroud)

并获得关于Foo抽象的编译时错误.但是CRTP没有提供这样的保护.我可以以某种方式实现它吗?运行时检查也是可以接受的.我想过将this->f指针与比较static_cast<Derived *>(this)->f,但没有设法让它工作.

Hol*_*olt 24

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

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

  • 您在哪里不确定这是否是上述评论中的标准定义行为.在这个答案中要注意扩展吗? (2认同)

Oli*_*liv 12

你可以使用这个解决方案,你可以拥有纯粹的"非虚拟抽象"功能,并尽可能地映射到CRTP这个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(){}
  };
Run Code Online (Sandbox Code Playgroud)


Joh*_*itb 12

这是另一种可能性:

#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
}
Run Code Online (Sandbox Code Playgroud)

对于GCC,它给出了一个非常明确的错误消息("错误:使用'auto Base :: g()[使用Derived = Foo]',然后扣除'auto'"),而对于Clang,它提供的可读性略低无限递归模板实例化Base<Foo>::g,g实例化自身但最终以错误结束.

  • 最新版本的clang [似乎生成](https://godbolt.org/g/ozbnVB)接近gcc(clang> = 3.9.1). (3认同)
  • @Holt宇宙已恢复秩序. (2认同)