保护CRTP模式调用
考虑以下标准CRTP例如:保护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
,但没有设法使其工作。
这里是另一种可能性:
#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>::g
,与g
实例化自己,但最终以错误结束。
您可以在编译时,这两个指针成员函数是不同的,例如:
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
不再是衍生所以你可能会选择一个更好的名字。
这个名称并不像修改后的语义那样糟糕。不能用'Derived'代替'Base
@StoryTeller它并不假装在语义上是相同的。它被标记为可能的(安全的)替代方案,在很多情况下都很好。(我一直都在使用它) – Galik
您可以使用此解决方案,你可以有纯粹的“非虚拟抽象”的功能,并尽可能多地映射到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(){}
};
我不知道这是否是标准定义的行为,但是您可以在'Base :: g'内'static_assert'指定'&Derived :: g!=&Base :: g'。 –
Holt
这似乎也工作:http://coliru.stacked-crooked.com/a/80021ad0bfb7aa47 –
@ JohannesSchaub-litb这很聪明!你应该写一个答案。 – Yakk