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

时间:2017-07-18 09:50:19

标签: c++ crtp pure-virtual virtual-method

考虑以下标准CRTP示例:

g

如果这是常规虚拟继承,我可以将虚拟struct Base { virtual void f() = 0; virtual void g() = 0; }; Foo方法标记为纯粹

this->f

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

4 个答案:

答案 0 :(得分: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(); 
    }
};

答案 1 :(得分:12)

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

答案 2 :(得分: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
}

对于GCC,它提供了一个非常明确的错误消息(“错误:在扣除'auto'”之前使用'auto Base :: g()[with Derived = Foo]',而对于Clang来说,它提供了一个稍微不那么可读的无限递归模板实例化Base<Foo>::gg实例化自身,但最终以错误结束。

答案 3 :(得分:0)

你可以考虑做这样的事情。您可以将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不再是派生的,因此您可以为它选择一个更好的名称。