从编译时已知为派生类的类调用虚方法时是否存在性能损失?下面我用派生类显式调用force_speak
。
代码:
#include <iostream>
#include <array>
#include <memory>
class Base
{
public:
virtual void speak()
{
std::cout << "base" << std::endl;
}
};
class Derived1 : public Base
{
public:
void speak()
{
std::cout << "derived 1" << std::endl;
}
};
template<class B>
void force_speak(std::array<std::unique_ptr<B>, 3>& arr)
{
for (auto& b: arr)
{
b->speak();
}
}
int main()
{
std::array<std::unique_ptr<Derived1>, 3> arr =
{
std::unique_ptr<Derived1>(new Derived1),
std::unique_ptr<Derived1>(new Derived1),
std::unique_ptr<Derived1>(new Derived1)
};
force_speak(arr);
return 0;
}
答案 0 :(得分:6)
从编译时已知为派生类的类调用虚方法时是否存在性能损失?请参阅下面的代码。
这取决于。大多数编译器将“去虚拟化”代码如下:
Derived1 d;
d.speak();
对象的动态类型在调用站点是已知的,因此编译器可以避免通过vtable进行调用,并且可以直接调用Derived1::speak()
。
在您的示例中,编译器需要更智能,因为在force_speak
中您只有Derived1*
个指针(存储在unique_ptr
个对象中)并且在该上下文中不清楚动态类型是否指向对象的是Derived1
或更多派生类型。编译器需要将对force_speak
的调用内联到main
(动态类型已知),或者使用一些关于类型的其他知识来允许虚拟化发生。 (作为附加知识的一个示例,整个程序优化可以确定 在程序中的任何位置都没有声明其他派生类型,因此Derived1*
必须指向一个Derived1
。)
使用C ++ 11 final
关键字可以帮助编译器对某些情况进行虚拟化,例如:如果Derived1
被标记为final
,则编译器知道Derived1*
只能指向Derived1
,而不是指向可能覆盖speak()
的其他类型的{{1}}
答案 1 :(得分:2)
它依赖于编译器。编译器必须静态地知道Derived1:speak()
是唯一的选择。这包括知道没有Derived2::speak()
定义,class Derived2: public Derived1
。但编译器可能根本不会实现这样的优化。
函数模板参数可以由编译器自动推导出来。这是C ++标准的一部分。编译器知道呼叫站点的参数类型,因此用户无需提供它。注意,用户可以提供类型,并且例如可以提供类型兼容但与实际参数的类型不同。
答案 2 :(得分:1)
在这种特定情况下,答案是可能。
如果编译器决定内联force_speak()
,理论上可以推断出数组中的所有指针都是Derived1
个实例,因此静态调用该方法。 (标准不要求这种优化,因此静态或虚拟地调用该方法取决于您的特定编译器以及编译期间可能使用的选项。)
如果编译器没有内联调用,则必须发出虚拟调用方法的指令,因为您可以进一步派生类Derived1
并再次覆盖该方法,存储实例在std::unique_ptr<Derived1>
中的那个类,并将其中的数组传递给函数。