Fra*_*ank 7 c++ oop inheritance
这是一个非常基本的问题,但我仍然不确定:
如果我有一个将被实例化数百万次的类 - 是否建议不从其他类派生它?换句话说,继承是否会带来一些成本(在内存或运行时方面构建或销毁对象),我应该在实践中担心?
例:
class Foo : public FooBase { // should I avoid deriving from FooBase?
// ...
};
int main() {
// constructs millions of Foo objects...
}
Run Code Online (Sandbox Code Playgroud)
Set*_*gie 20
从类继承在运行时不需要任何成本.
如果你在基类中有变量,那么类实例当然会占用更多的内存,但是如果它们直接在派生类中并且你没有继承任何东西,那么它们就会占用更多内存.
这没有考虑到virtual会导致运行时成本较低的方法.
tl;博士:你不应该担心它.
到目前为止,我对一些回复/评论感到有些惊讶......
继承是否带来一些成本(就记忆而言)
是.鉴于:
namespace MON {
class FooBase {
public:
FooBase();
virtual ~FooBase();
virtual void f();
private:
uint8_t a;
};
class Foo : public FooBase {
public:
Foo();
virtual ~Foo();
virtual void f();
private:
uint8_t b;
};
class MiniFoo {
public:
MiniFoo();
~MiniFoo();
void f();
private:
uint8_t a;
uint8_t b;
};
class MiniVFoo {
public:
MiniVFoo();
virtual ~MiniVFoo();
void f();
private:
uint8_t a;
uint8_t b;
};
} // << MON
extern "C" {
struct CFoo {
uint8_t a;
uint8_t b;
};
}
Run Code Online (Sandbox Code Playgroud)
在我的系统上,大小如下:
32 bit:
FooBase: 8
Foo: 8
MiniFoo: 2
MiniVFoo: 8
CFoo: 2
64 bit:
FooBase: 16
Foo: 16
MiniFoo: 2
MiniVFoo: 16
CFoo: 2
Run Code Online (Sandbox Code Playgroud)
用于构造或销毁对象的运行时
额外的功能开销和需要的虚拟分派(包括适当的析构函数).这可能会花费很多,并且可能/不能执行一些非常明显的优化,例如内联.
整个主题要复杂得多,但这会让你了解成本.
如果速度或大小真正关键,那么您通常可以使用静态多态(例如模板)来实现性能和易于编程之间的出色平衡.
关于cpu性能,我创建了一个简单的测试,它在堆栈和堆上创建了数百万个这样的类型并调用了f,结果是:
FooBase 16.9%
Foo 16.8%
Foo2 16.6%
MiniVFoo 16.6%
MiniFoo 16.2%
CFoo 15.9%
Run Code Online (Sandbox Code Playgroud)
注意:Foo2来自foo
在测试中,分配被添加到矢量,然后被删除.没有这个阶段,CFoo就完全被优化了.正如Jeff Dege在他的回答中所说,分配时间将是这项测试的重要部分.
从样本中修剪分配函数和向量create/destroy会产生以下数字:
Foo 19.7%
FooBase 18.7%
Foo2 19.4%
MiniVFoo 19.3%
MiniFoo 13.4%
CFoo 8.5%
Run Code Online (Sandbox Code Playgroud)
这意味着虚拟变体占用CFoo的两倍,以执行其构造函数,析构函数和调用,而MiniFoo的速度约为1.5倍.
我们正在进行分配:如果您可以使用单一类型进行实施,那么您还可以减少在此方案中必须进行的分配数量,因为您可以分配1M对象的数组,而不是创建1M地址列表和然后用独特的新类型填充它.当然,有专门的分配器可以减轻这个重量.由于分配/空闲时间是此测试的权重,因此可以显着减少分配和释放对象所花费的时间.
Create many MiniFoos as array 0.2%
Create many CFoos as array 0.1%
Run Code Online (Sandbox Code Playgroud)
还要记住,MiniFoo和CFoo的大小消耗每个元素的内存的1/4 - 1/8,并且连续分配消除了存储指向动态对象的指针的需要.然后,您可以在实现中以更多方式(指针或索引)跟踪对象,但是数组也可以显着减少客户端上的分配demends(uint32_t vs 64位arch上的指针) - 加上系统所需的所有簿记对于分配(在处理如此多的小分配时这是很重要的).
具体而言,此测试中的大小消耗:
32 bit
267MB for dynamic allocations (worst)
19MB for the contiguous allocations
64 bit
381MB for dynamic allocations (worst)
19MB for the contiguous allocations
Run Code Online (Sandbox Code Playgroud)
这意味着所需的内存减少了十多个,分配/释放所花费的时间明显好于此!
静态调度实现与混合或动态调度相比可以快几倍.这通常为优化者提供了更多机会来查看更多程序并相应地对其进行优化.
在实践中,动态类型倾向于导出更多符号(方法,dtors,vtable),这可能会显着增加二进制大小.
假设这是您的实际用例,那么您可以显着提高性能和资源使用率.我提出了一些主要的优化方案......以防万一有人认为以这种方式改变设计就有资格作为"微观优化".