C++ C+中的CRTP调度+;11
假设我有以下代码:C++ C+中的CRTP调度+;11,c++,templates,c++11,crtp,C++,Templates,C++11,Crtp,假设我有以下代码: template <class Derived> class Base { public: virtual void foo_impl() = 0; void foo() { static_cast<Derived*>(this)->foo_impl(); //A (*static_cast<Derived*>(this)).foo_impl(); //B } }; class Derive
template <class Derived>
class Base {
public:
virtual void foo_impl() = 0;
void foo() {
static_cast<Derived*>(this)->foo_impl(); //A
(*static_cast<Derived*>(this)).foo_impl(); //B
}
};
class Derived : public Base<Derived> {
private:
void foo_impl() {
bar();
}
};
模板
阶级基础{
公众:
虚拟void foo_impl()=0;
void foo(){
静态_cast(this)->foo_impl();//A
(*static_cast(this)).foo_impl();//B
}
};
派生类:公共基{
私人:
void foo_impl(){
bar();
}
};
有几个问题:
行A是否会生成虚拟函数调用?尽管我在互联网上找到的大多数东西都建议这样做,但考虑到指向派生的指针实际上仍然可以指向类型为Derived2(其中Derived2:public-Derived)的对象,我不知道编译器如何进行静态调度
B行是否解决了我在上一点中提出的问题(如果适用)?考虑到现在调用不再是指针上的调用,因此使用*,它看起来是这样的。将避免虚拟函数调用。但是,如果编译器将取消引用的强制转换视为引用类型,它仍然可以生成虚拟函数调用。。。在这种情况下,解决方法是什么
将C++11 final关键字添加到foo_impl()是否会改变编译器在任何(或任何其他相关)情况下的行为
行A是否会生成虚拟函数调用
是foo_impl()
是虚拟的,派生的
覆盖它。尽管Derived
中的foo\u impl()
没有显式标记为virtual
,但它在基类中,这足以使它成为一个虚拟函数
B行是否解决了我在上一点中提出的问题(如果适用)
否。调用是在指针上还是在引用上并不重要:编译器仍然不知道您是在从Derived
派生的类的实例上调用函数foo\u impl()
,还是在Derived
的直接实例上调用函数。因此,通过vtable执行调用
要明白我的意思:
#include <iostream>
using namespace std;
template <class Derived>
class Base {
public:
virtual void foo_impl() = 0;
void foo() {
static_cast<Derived*>(this)->foo_impl();
(*static_cast<Derived*>(this)).foo_impl();
}
};
class Derived : public Base<Derived> {
public:
void foo_impl() {
cout << "Derived::foo_impl()" << endl;
}
};
class MoreDerived : public Derived {
public:
void foo_impl() {
cout << "MoreDerived::foo_impl()" << endl;
}
};
int main()
{
MoreDerived d;
d.foo(); // Will output "MoreDerived::foo_impl()" twice
}
#包括
使用名称空间std;
模板
阶级基础{
公众:
虚拟void foo_impl()=0;
void foo(){
静态_cast(this)->foo_impl();
(*static_cast(this)).foo_impl();
}
};
派生类:公共基{
公众:
void foo_impl(){
难道A行和B行中多余的措辞对你的行为毫无影响吗
生成的代码。我不知道是谁推荐这个(我从未见过)
但在实践中,它可能产生影响的唯一时间是
如果函数不是虚拟的,只需编写foo\u impl()
,然后
完了
如果
编译器知道派生类型。我见过它用于
类向量类(其中有不同的实现,
e、 g.向量的法线、稀疏度等):
模板
阶级基础
{
私人:
虚拟T&getValue(int索引)=0;
公众:
T&运算符[](int-index){return getValue(index);}
};
模板
派生类:公共基
{
私人:
虚拟T&getValue(整数索引)
{
返回运算符[](索引);
}
公众:
T&运算符[](索引)
{
//找到元素并返回它。
}
};
这里的想法是,您通常只通过引用进行工作
对于基类,但是如果性能成为问题,因为
如果您在一个紧密的循环中使用[]
,您可以动态地向
在循环之前使用派生类,并在派生类上使用[]
类。CRTP的常用习惯用法不涉及在基中声明纯虚函数。正如您在其中一条注释中提到的,这意味着编译器将不会强制执行派生类型中的成员定义(除了通过使用之外,如果在基中使用了foo
,则需要在派生类型中存在foo_impl
)
虽然我会坚持使用常见的习惯用法,不在基础中定义纯虚拟函数,但是,如果您确实觉得需要这样做,可以通过添加额外的限定来禁用动态分派:
template <class Derived>
class Base {
public:
virtual void foo_impl() = 0;
void foo() {
static_cast<Derived*>(this)->Derived::foo_impl();
// ^^^^^^^^^
}
};
模板
阶级基础{
公众:
虚拟void foo_impl()=0;
void foo(){
静态_cast(this)->派生::foo_impl();
// ^^^^^^^^^
}
};
使用额外的限定条件Derived::
将禁用动态分派,该调用将静态解析为Derived::foo_impl
。请注意,这将带来所有常见的警告:您有一个带有虚拟函数的类,并为每个对象支付虚拟指针的费用,但您不能重写该虚拟函数函数是最派生的类型,因为CRTP基中使用的是阻塞动态分派…语句a和B是100%等效的,根据内置的->
运算符的定义。语句a和B是100%等效的foo\u impl()
。额外的赘述就是这样。调用不是在指针上,而是在左值引用上-正如所说的,语义相同。我认为当函数声明为final
时,编译器可能有足够的信息来进行静态调用,而不是虚拟调用,作为一种优化。不知道这种可能性有多大。好吧,那么,有没有办法在没有C++概念的情况下强制执行子类的接口呢?也许是表达式sFIEAE还是StasyAsAsDt?我已经看到了一些错误消息的样子……@罗伯逊:如果程序使用<代码> BAS::FoO ,那么编译器会高兴地尖叫:<代码>派生::FooYiIMPL/<代码>没有声明。e您需要什么?您可以禁用动态分派(添加了一个关于如何执行的答案),但真正的问题是您是否真的需要额外的vptr和复杂性来完成任何事情,或者您是否在不必要地使事情复杂化。@RobertMason:您可以通过调用foo_impl()的函数来自动实现这一点
在Base
类中。如果您被错误消息的长度所困扰
template <class Derived>
class Base {
public:
virtual void foo_impl() = 0;
void foo() {
static_cast<Derived*>(this)->Derived::foo_impl();
// ^^^^^^^^^
}
};