C++11中的CRTP调度
CRTP Dispatch in C++11
假设我有以下代码:
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();
}
};
几个问题:
线路A会生成一个虚拟函数调用吗?尽管我在互联网上找到的大多数建议都是这样做的,但对我来说,考虑到指向Derived的指针实际上仍然可以指向Derived2类型的对象,其中Derived2:public Derived,我看不出编译器如何进行静态调度。
B行是否解决了我在前一点中提出的问题(如果适用)?考虑到现在调用不再在指针上,因此使用了*,似乎会这样。将避免虚拟函数调用。但是,如果编译器将取消引用的强制转换视为引用类型,它仍然可以生成虚拟函数调用。。。在这种情况下,解决方法是什么?
在foo_impl()中添加C++11 final关键字是否会改变编译器在任何(或任何其他相关)情况下的操作方式?
线路A会生成一个虚拟函数调用吗?
是。foo_impl()
是虚拟的,Derived
覆盖它。即使Derived
中的foo_impl()
没有显式标记为virtual
,但它在基类中,这足以使它成为一个虚拟函数。
B行是否解决了我在前一点中提出的问题(如果适用)?
否。调用是在指针上还是在引用上并不重要:编译器仍然不知道您是在从Derived
派生的类的实例上调用函数foo_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
}
最后:
在
foo_impl()
中添加C++11 final关键字是否会改变编译器在任何(或任何其他相关)情况下的操作方式?
理论上是的。final
关键字将使其无法在Derived
的子类中覆盖该函数。因此,当通过指向Derived
的指针执行对foo_impl()
的函数调用时,编译器可以静态地解析该调用。然而,据我所知,C++标准并不要求编译器这样做。
结论:
无论如何,我相信您实际想要做的是根本不在基类中声明foo_impl()
函数。使用CRTP时通常会出现这种情况。此外,如果希望类Base<Derived>
访问Derived
的private
函数foo_impl()
,则必须将其声明为Derived
的friend
。否则,可以将foo_impl()
公开。
CRTP的常用用法不涉及在基中声明纯虚拟函数。正如您在其中一条注释中提到的,这意味着编译器不会在派生类型中强制执行成员的定义(除了通过使用之外,如果在基中使用了foo
,则需要在衍生类型中存在foo_impl
)。
虽然我会坚持常见的习惯用法,不在基础中定义纯虚拟函数,但是,如果你真的觉得需要这样做,你可以通过添加额外的限定来禁用动态调度:
template <class Derived>
class Base {
public:
virtual void foo_impl() = 0;
void foo() {
static_cast<Derived*>(this)->Derived::foo_impl();
// ^^^^^^^^^
}
};
使用额外的限定Derived::
将禁用动态调度,并且该调用将静态解析为Derived::foo_impl
。请注意,这将带来所有常见的注意事项:您有一个带有虚拟函数的类,并为每个对象支付虚拟指针的成本,但您不能在最派生的类型中覆盖该虚拟函数,因为CRTP库中的使用正在阻止动态调度。。。
第A行和第B行中的额外措辞对生成的代码。我不知道是谁推荐这个(我从没见过它),但在实践中,它可能产生影响的唯一时间是如果函数不是虚拟的。只要写foo_impl()
如果编译器知道派生类型。我见过它向量类(在存在不同实现的情况下,例如向量的正常、稀疏等):
template <typename T>
class Base
{
private:
virtual T& getValue( int index ) = 0;
public:
T& operator[]( int index ) { return getValue( index ); }
};
template <typename T>
class Derived : public Base<T>
{
private:
virtual T& getValue( int index )
{
return operator[]( index );
}
public:
T& operator[]( index )
{
// find element and return it.
}
};
这里的想法是,你通常只通过引用来工作到基类,但如果性能成为问题,因为如果在紧密循环中使用[]
,则可以将dynamic_cast
循环之前的派生类,并在派生类上使用[]
班
- 如何在c++中实现处理器调度模拟器
- 尝试使用继承和模板实现CRTP.Visual Studio正在生成编译器错误
- CRTP 单一实例不完整类型或非文本类型
- 如何在 C++17 STL 并行算法中处理调度?
- CRTP 中的复制赋值运算符 - gcc vs clang 和 msvc
- 无法使用迭代器标记调度实例化模板
- 将具有固定签名的自定义函数名称注入 CRTP
- 如何在MISRA C++之后实施CRTP
- 检测使用 CRTP 的类中的成员函数
- 简化使用 CRTP 模式的类的声明
- C++ CRTP initialization
- 在 c++11 中为 pthread 设置调度参数
- 如何在 assert() 和 static_assert() 之间调度,如果在 constexpr 上下文中依赖?
- 概念可以与 CRTP 习语一起使用吗?
- CRTP - 危险的内存访问?
- 如何断言 CRTP 的函数为最终函数?
- 如何使用从处理程序调度的最终回调将响应异步返回给调用方on_read?
- CRTP 模式不会触发完整的模板实例化
- C++双重调度
- C++11中的CRTP调度