防止 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
}
如果这是常规虚拟继承,我可以将虚拟 f
和 g
方法标记为纯方法
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>::g
,g
实例化自身但最终以错误结束。
考虑以下标准 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
}
如果这是常规虚拟继承,我可以将虚拟 f
和 g
方法标记为纯方法
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>::g
,g
实例化自身但最终以错误结束。