当我知道类型时,如何避免虚拟呼叫
How can I avoid a virtual call when I know the type?
请考虑以下代码片段:
struct Base { virtual void func() { } };
struct Derived1 : Base { void func() override { print("1"); } };
struct Derived2 : Base { void func() override { print("2"); } };
class Manager {
std::vector<std::unique_ptr<Base>> items;
public:
template<class T> void add() { items.emplace_back(new T); }
void funcAll() { for(auto& i : items) i->func(); }
};
int main() {
Manager m;
m.add<Derived1>();
m.add<Derived2>();
m.funcAll(); // prints "1" and "2"
};
我正在使用virtual
调度,以便从多态对象的std::vector
调用正确的override
方法。
但是,我知道多态对象的类型,因为我在 Manager::add<T>
中指定了它。
我的想法是通过获取成员函数的地址并将其直接存储在某个地方来避免virtual
调用T::func()
。但是这是不可能的,因为我需要将其存储为 void*
并将其重新投射回 Manager::funcAll()
中,但当时我没有类型信息。
我的问题是:在这种情况下,我似乎比平时拥有更多的多态性信息(用户在Manager::add<T>
中指定派生类型T
) - 有什么方法可以使用这种类型的信息来防止看似不需要的virtual
调用?(但是,用户应该能够创建自己的类,这些类派生自其代码中的Base
。
但是,我知道多态对象的类型,因为我在
Manager::add<T>
中指定了它。
不,你没有。在add
内,您知道要添加的对象的类型;但是,您可以添加不同类型的对象,就像您在示例中所做的那样。funcAll
无法静态确定元素的类型,除非您参数化Manager
只处理一种类型。
如果您确实知道该类型,则可以非虚拟方式调用该函数:
i->T::func();
但是,重申一下,您不能在此处静态确定类型。
如果我理解得很好,您希望您的 add 方法(即获取对象的类)根据该对象类在您的向量中存储正确的函数。您的向量只包含函数,没有关于对象的更多信息。
您有点想在调用虚拟调用之前"解决"虚拟调用。在以下情况下,这可能很有趣:然后多次调用该函数,因为您没有每次都求解虚的开销。
因此,您可能希望使用与"虚拟"类似的过程,即使用"虚拟表"。虚拟的实现是在低级别完成的,与你想出的任何东西相比,速度非常快,所以同样,在它变得有趣之前,应该调用很多次函数。
有时可以帮助的一个技巧是按类型对向量进行排序(您应该能够使用 add() 函数中可用的类型知识来强制执行这一点),如果元素的顺序无关紧要。如果您主要要迭代向量以调用虚函数,这将有助于 CPU 的分支预测器预测调用的目标。或者,您可以在管理器中为每种类型维护单独的向量,并依次迭代它们,这具有类似的效果。
编译器的优化器也可以帮助您处理此类代码,特别是如果它支持按配置优化 (POGO)。编译器可以在某些情况下对调用进行非虚拟化,或者使用 POGO 可以在生成的程序集中执行一些操作以帮助 CPU 的分支预测器,例如测试最常见的类型,并对那些回退到间接调用不太常见的类型执行直接调用。
下面是一个测试程序的结果,它说明了按类型排序的性能优势,管理器是你的版本,Manager2 维护一个按 typeid 索引的向量的哈希表:
Derived1::count = 50043000, Derived2::count = 49957000
class Manager::funcAll took 714ms
Derived1::count = 50043000, Derived2::count = 49957000
class Manager2::funcAll took 274ms
Derived1::count = 50043000, Derived2::count = 49957000
class Manager2::funcAll took 273ms
Derived1::count = 50043000, Derived2::count = 49957000
class Manager::funcAll took 714ms
测试代码:
#include <iostream>
#include <vector>
#include <memory>
#include <random>
#include <unordered_map>
#include <typeindex>
#include <chrono>
using namespace std;
using namespace std::chrono;
static const int instanceCount = 100000;
static const int funcAllIterations = 1000;
static const int numTypes = 2;
struct Base { virtual void func() = 0; };
struct Derived1 : Base { static int count; void func() override { ++count; } };
int Derived1::count = 0;
struct Derived2 : Base { static int count; void func() override { ++count; } };
int Derived2::count = 0;
class Manager {
vector<unique_ptr<Base>> items;
public:
template<class T> void add() { items.emplace_back(new T); }
void funcAll() { for (auto& i : items) i->func(); }
};
class Manager2 {
unordered_map<type_index, vector<unique_ptr<Base>>> items;
public:
template<class T> void add() { items[type_index(typeid(T))].push_back(make_unique<T>()); }
void funcAll() {
for (const auto& type : items) {
for (auto& i : type.second) {
i->func();
}
}
}
};
template<typename Man>
void Test() {
mt19937 engine;
uniform_int_distribution<int> d(0, numTypes - 1);
Derived1::count = 0;
Derived2::count = 0;
Man man;
for (auto i = 0; i < instanceCount; ++i) {
switch (d(engine)) {
case 0: man.add<Derived1>(); break;
case 1: man.add<Derived2>(); break;
}
}
auto startTime = high_resolution_clock::now();
for (auto i = 0; i < funcAllIterations; ++i) {
man.funcAll();
}
auto endTime = high_resolution_clock::now();
cout << "Derived1::count = " << Derived1::count << ", Derived2::count = " << Derived2::count << "n"
<< typeid(Man).name() << "::funcAll took " << duration_cast<milliseconds>(endTime - startTime).count() << "ms" << endl;
}
int main() {
Test<Manager>();
Test<Manager2>();
Test<Manager2>();
Test<Manager>();
}
- C++避免重复声明的语法是什么
- 在没有太多条件句的情况下,我如何避免被零除
- 虚拟决赛作为安全
- 如何重构类层次结构以避免菱形问题
- 避免C++虚拟继承
- 如何避免重复的const和非const虚拟函数?是否可以
- 在这种情况下是否可以避免使用虚拟方法调用?
- 避免使用虚拟模板函数
- 避免虚拟调用循环的模式
- 如何避免虚拟关键字
- 将临时对象保留在寄存器上以避免虚拟机中的额外存储/加载
- 如何避免在具有其他虚拟基类时对基类函数的不明确调用
- 有没有办法避免在特定自定义类继承结构中使用虚拟方法
- 当我知道类型时,如何避免虚拟呼叫
- 重载虚拟方法与非虚拟方法有何不同
- 在使用虚拟继承时,我可以避免重复的基类初始化吗
- 避免无休止的虚拟查找
- 对象层次结构"Implementation" - "the easiest way"或如何避免虚拟继承?
- 避免构造函数中的虚拟方法
- C++:通过调用纯虚拟函数来避免重复