C++ 保护CRTP模式不受“中堆栈溢出”的影响;“纯虚拟”;电话

C++ 保护CRTP模式不受“中堆栈溢出”的影响;“纯虚拟”;电话,c++,crtp,pure-virtual,virtual-functions,C++,Crtp,Pure Virtual,Virtual Functions,考虑以下标准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> {

考虑以下标准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
}

并得到一个关于
Foo
是抽象的编译时错误。但CRTP并没有提供这样的保护。我能实现它吗?运行时检查也是可以接受的。我曾考虑过将
this->f
指针与
static_cast(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(); 
    }
};
模板
结构基{
void g(){
静态断言(&派生::g!=&Base::g),
“派生类必须实现g()”;
静态_cast(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
}
模板
结构基{
void f(){d.f();}
void g(){d.g();}
私人:
导出d;
};
结构FooImpl{

void f(){std::cout您可以使用此解决方案,您可以使用纯“非虚拟抽象”函数,并且它尽可能多地映射到CRTP:

模板
结构基
{
void f(){static_cast(this)->do_f();}
void g(){static_cast(this)->do_g();}
私人:
//派生必须实现do\u f
void do_f()=删除;
//将do_g作为默认实现
void do_g(){}
};
结构派生
:基本
{
友元结构库;
私人:
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
}
#包括
模板
结构基{
自动f(){return static_cast(this)->f();}
自动g(){return static_cast(this)->g();}
};
struct Foo:公共基{

void f(){std::cout我不知道这是否是一个标准定义的行为,但是你可以在
Base::g
static\u assert
这个
&Derived::g!=&Base::g
。这似乎也很有效:@johanneschaub litb这很聪明!你应该写一个答案。@yakdone,谢谢。Clang的错误消息是次优的,但名字不是与更改的语义一样糟糕。不能再用
派生的
替换
基的
。因此,通用函数a-la
模板void foo(Base&){}
将不再工作。@StoryTeller它不会假装语义相同。它被标记为一种可能的(安全的)替代方法,在许多情况下都很好。(我一直在使用它)你不确定这是否是上面一条评论中的标准定义行为。在这个回答中是否要进一步说明?@Yakk我不确定基类/派生类之间指向成员函数的指针的规则(转换/比较)。我检查了标准(特别是[conv.mem#2])还有一些问题,我很有信心这是正确的。但如果你有理由相信这是不正确的,请随意解释-如果这是错误的,我会很快删除这个答案。根据我的经验,这实际上是很罕见的,这种叮当声比gcc发出的错误信息更糟糕。最新版本的叮当声接近gcc(叮当>=3.9.1)。错误消息可能不太完美,但你不能否认解决方案的简单性!很好,但有可能将其应用于C++11吗?@uranix可以采用类似的方法,但随后错误消息和代码都变得更加丑陋:
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
}