C++ 保护CRTP模式不受“中堆栈溢出”的影响;“纯虚拟”;电话
考虑以下标准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> {
#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
}