减少多虚拟继承中对象的大小(浪费)
reduce size of object (wasted) in Multi virtual inheritance
分析后,我发现我的程序有很大一部分内存被多虚拟继承浪费了。
这是 MCVE 来演示问题 ( http://coliru.stacked-crooked.com/a/0509965bea19f8d9 )
#include<iostream>
class Base{
public: int id=0;
};
class B : public virtual Base{
public: int fieldB=0;
public: void bFunction(){
//do something about "fieldB"
}
};
class C : public virtual B{
public: int fieldC=0;
public: void cFunction(){
//do something about "fieldC"
}
};
class D : public virtual B{
public: int fieldD=0;
};
class E : public virtual C, public virtual D{};
int main (){
std::cout<<"Base="<<sizeof(Base)<<std::endl; //4
std::cout<<"B="<<sizeof(B)<<std::endl; //16
std::cout<<"C="<<sizeof(C)<<std::endl; //32
std::cout<<"D="<<sizeof(D)<<std::endl; //32
std::cout<<"E="<<sizeof(E)<<std::endl; //56
}
希望sizeof(E)
不要超过16个字节(id
+fieldB
+fieldC
+fieldD
).
根据实验,如果是非虚拟继承,E
的大小将是24(MCVE)。
如何减小 E
的大小(通过 C++ 魔法、更改程序架构或设计模式)?
要求:-
Base,B,C,D,E
不能作为模板 class。这会对我造成循环依赖。
- 我必须能够从派生 class(如果有的话)调用基 class 的 函数 ,例如
e->bFunction()
和 e->cFunction()
,一如既往。
但是,如果我不能再调用 e->bField
也没关系。
- 我还是想要申报方便
目前,我可以轻松地将 "E inherit from C and D"
声明为 class E : public virtual C, public virtual D
。
我正在考虑 CRTP,例如class E: public SomeTool<E,C,D>{}
,但不确定如何让它发挥作用。
让事情变得简单:
- 在我的例子中,每个 class 都像整体一样使用,即我永远不会在
static_cast<C*>(E*)
之类的类型之间转换对象,反之亦然。
- 允许使用宏,但不鼓励使用。
- 允许使用 Pimpl 习语。其实下面是我的白日梦
也许,我可以删除所有虚拟继承。
但是,根据所有要求,我找不到编码方法。
在 pimpl 中,如果我让 E
virtual inherit from C & D
等,上述所有要求都将得到满足,但我仍然会浪费大量内存。 :-
我正在使用 C++17。
编辑
这是对我现实生活中问题的更正确的描述。
我创建了一个包含许多组件的游戏,例如B C D E
.
它们都是通过池创建的。因此,它可以实现快速迭代。
目前,如果我从游戏引擎中查询每个 E
,我将能够调用 e->bFunction()
.
在我最严重的情况下,我在 E
-like class 中每个对象浪费 104 个字节。 (真正的层次结构更复杂)
编辑 3
让我再试一次。这是一个更有意义的class图表。
我有一个中央系统可以自动分配 hpPtr
、flyPtr
、entityId
、componentId
、typeId
。
也就是说,不用担心它们是如何初始化的。
真实情况下,恐怖钻石发生的情况很多class,这是最简单的情况。
目前,我的电话是这样的:-
auto hps = getAllComponent<HpOO>();
for(auto ele: hps){ ele->damage(); }
auto birds = getAllComponent<BirdOO>();
for(auto ele: birds ){
if(ele->someFunction()){
ele->suicidalFly();
//.... some heavy AI algorithm, etc
}
}
通过这种方法,我可以像在实体组件系统中一样享受缓存一致性,以及像在对象中一样酷炫的 ctrl+space
HpOO
、FlyableOO
和 BirdOO
智能感知- 导向风格。
一切正常 - 只是占用了太多内存。
编辑:基于问题的最新更新和一些聊天
这是在所有 classes 中维护虚拟的最紧凑的方法。
#include <iostream>
#include <vector>
using namespace std;
struct BaseFields {
int entityId{};
int16_t componentId{};
int8_t typeId{};
int16_t hpIdx;
int16_t flyPowerIdx;
};
vector<int> hp; // this will contain all the hit points, dynamically resizable, logic up to you
vector<float> flyPower; // this will contain all the fly powers, dynamically resizable, logic up to you
class BaseComponent {
public: // or protected
BaseFields data;
};
class HpOO : public virtual BaseComponent {
public:
void damage() {
hp[data.hpIdx] -= 1;
}
};
class FlyableOO : public virtual BaseComponent {
public:
void addFlyPower(float power) {
flyPower[data.hpIdx] += power;
}
};
class BirdOO : public virtual HpOO, public virtual FlyableOO {
public:
void suicidalFly() {
damage();
addFlyPower(5);
}
};
int main (){
std::cout<<"Base="<<sizeof(BaseComponent)<<std::endl; // 12
std::cout<<"C="<<sizeof(HpOO)<<std::endl; // 24
std::cout<<"D="<<sizeof(FlyableOO)<<std::endl; // 24
std::cout<<"E="<<sizeof(BirdOO)<<std::endl; // 32
}
更小的 class 大小版本删除所有虚拟 class 东西:
#include <iostream>
#include <vector>
using namespace std;
struct BaseFields {
};
vector<int> hp; // this will contain all the hit points, dynamically resizable, logic up to you
vector<float> flyPower; // this will contain all the fly powers, dynamically resizable, logic up to you
class BaseComponent {
public: // or protected
int entityId{};
int16_t componentId{};
int8_t typeId{};
int16_t hpIdx;
int16_t flyPowerIdx;
protected:
void damage() {
hp[hpIdx] -= 1;
};
void addFlyPower(float power) {
flyPower[hpIdx] += power;
}
void suicidalFly() {
damage();
addFlyPower(5);
};
};
class HpOO : public BaseComponent {
public:
using BaseComponent::damage;
};
class FlyableOO : public BaseComponent {
public:
using BaseComponent::addFlyPower;
};
class BirdOO : public BaseComponent {
public:
using BaseComponent::damage;
using BaseComponent::addFlyPower;
using BaseComponent::suicidalFly;
};
int main (){
std::cout<<"Base="<<sizeof(BaseComponent)<<std::endl; // 12
std::cout<<"C="<<sizeof(HpOO)<<std::endl; // 12
std::cout<<"D="<<sizeof(FlyableOO)<<std::endl; // 12
std::cout<<"E="<<sizeof(BirdOO)<<std::endl; // 12
// accessing example
constexpr int8_t BirdTypeId = 5;
BaseComponent x;
if( x.typeId == BirdTypeId ) {
auto y = reinterpret_cast<BirdOO *>(&x);
y->suicidalFly();
}
}
这个例子假设你的派生 classes 没有重叠的功能和不同的效果,如果你有那些你必须向你的基础 class 添加虚函数以获得 12 字节的额外开销(如果打包 class,则为 8)。
而且很可能是最小的版本仍然保持虚拟
#include <iostream>
#include <vector>
using namespace std;
struct BaseFields {
int entityId{};
int16_t componentId{};
int8_t typeId{};
int16_t hpIdx;
int16_t flyPowerIdx;
};
#define PACKED [[gnu::packed]]
vector<int> hp; // this will contain all the hit points, dynamically resizable, logic up to you
vector<float> flyPower; // this will contain all the fly powers, dynamically resizable, logic up to you
vector<BaseFields> baseFields;
class PACKED BaseComponent {
public: // or protected
int16_t baseFieldIdx{};
};
class PACKED HpOO : public virtual BaseComponent {
public:
void damage() {
hp[baseFields[baseFieldIdx].hpIdx] -= 1;
}
};
class PACKED FlyableOO : public virtual BaseComponent {
public:
void addFlyPower(float power) {
flyPower[baseFields[baseFieldIdx].hpIdx] += power;
}
};
class PACKED BirdOO : public virtual HpOO, public virtual FlyableOO {
public:
void suicidalFly() {
damage();
addFlyPower(5);
}
};
int main (){
std::cout<<"Base="<<sizeof(BaseComponent)<<std::endl; // 2
std::cout<<"C="<<sizeof(HpOO)<<std::endl; // 16 or 10
std::cout<<"D="<<sizeof(FlyableOO)<<std::endl; // 16 or 10
std::cout<<"E="<<sizeof(BirdOO)<<std::endl; // 24 or 18
}
第一个数字用于解压缩结构,第二个数字用于压缩
您还可以使用联合技巧将 hpIdx 和 flyPowerIdx 打包到 entityId 中:
union {
int32_t entityId{};
struct {
int16_t hpIdx;
int16_t flyPowerIdx;
};
};
在上面的示例中,如果不使用打包并将整个 BaseFields
结构移动到 BaseComponent
class 中,大小将保持不变。
结束编辑
虚拟继承只是在 class 的基础上增加一个指针大小,加上指针对齐(如果需要)。如果你真的需要一个虚拟的 class.
,你就无法解决这个问题
您应该问自己的问题是您是否真的需要它。根据您访问此数据的方法,情况可能并非如此。
考虑到您需要虚拟继承,但需要从所有 classes 调用所有常用方法,您可以拥有一个虚拟基础 class 并且使用比 space 少一点您的原始设计如下:
class Base{
public: int id=0;
virtual ~Base();
// virtual void Function();
};
class B : public Base{
public: int fieldB=0;
// void Function() override;
};
class C : public B{
public: int fieldC=0;
};
class D : public B{
public: int fieldD=0;
};
class E : public C, public D{
};
int main (){
std::cout<<"Base="<<sizeof(Base)<<std::endl; //16
std::cout<<"B="<<sizeof(B)<<std::endl; // 16
std::cout<<"C="<<sizeof(C)<<std::endl; // 24
std::cout<<"D="<<sizeof(D)<<std::endl; // 24
std::cout<<"E="<<sizeof(E)<<std::endl; // 48
}
在缓存未命中但 CPU 仍然有能力处理结果的情况下,您可以使用 compiler-specific 指令进一步减小大小,使数据结构尽可能小(下一个例子适用于 gcc):
#include<iostream>
class [[gnu::packed]] Base {
public:
int id=0;
virtual ~Base();
virtual void bFunction() { /* do nothing */ };
virtual void cFunction() { /* do nothing */ }
};
class [[gnu::packed]] B : public Base{
public: int fieldB=0;
void bFunction() override { /* implementation */ }
};
class [[gnu::packed]] C : public B{
public: int fieldC=0;
void cFunction() override { /* implementation */ }
};
class [[gnu::packed]] D : public B{
public: int fieldD=0;
};
class [[gnu::packed]] E : public C, public D{
};
int main (){
std::cout<<"Base="<<sizeof(Base)<<std::endl; // 12
std::cout<<"B="<<sizeof(B)<<std::endl; // 16
std::cout<<"C="<<sizeof(C)<<std::endl; // 20
std::cout<<"D="<<sizeof(D)<<std::endl; // 20
std::cout<<"E="<<sizeof(E)<<std::endl; //40
}
以可能 CPU 开销的代价节省额外的 8 个字节(但如果内存是问题可能会有所帮助)。
此外,如果您确实为每个 classes 调用了一个函数,您应该只将它作为一个函数,在必要时覆盖它。
#include<iostream>
class [[gnu::packed]] Base {
public:
virtual ~Base();
virtual void specificFunction() { /* implementation for Base class */ };
int id=0;
};
class [[gnu::packed]] B : public Base{
public:
void specificFunction() override { /* implementation for B class */ }
int fieldB=0;
};
class [[gnu::packed]] C : public B{
public:
void specificFunction() override { /* implementation for C class */ }
int fieldC=0;
};
class [[gnu::packed]] D : public B{
public:
void specificFunction() override { /* implementation for D class */ }
int fieldD=0;
};
class [[gnu::packed]] E : public C, public D{
void specificFunction() override {
// implementation for E class, example:
C::specificFunction();
D::specificFunction();
}
};
这还可以让您避免在调用适当的函数之前弄清楚 class 哪个对象是什么。
此外,假设您最初的虚拟 class 继承想法最适合您的应用程序,您可以重组数据,以便更容易访问缓存目的,同时减小 classes 并同时访问您的函数:
#include <iostream>
#include <array>
using namespace std;
struct BaseFields {
int id{0};
};
struct BFields {
int fieldB;
};
struct CFields {
int fieldB;
};
struct DFields {
int fieldB;
};
array<BaseFields, 1024> baseData;
array<BaseFields, 1024> bData;
array<BaseFields, 1024> cData;
array<BaseFields, 1024> dData;
struct indexes {
uint16_t baseIndex; // index where data for Base class is stored in baseData array
uint16_t bIndex; // index where data for B class is stored in bData array
uint16_t cIndex;
uint16_t dIndex;
};
class Base{
indexes data;
};
class B : public virtual Base{
public: void bFunction(){
//do something about "fieldB"
}
};
class C : public virtual B{
public: void cFunction(){
//do something about "fieldC"
}
};
class D : public virtual B{
};
class E : public virtual C, public virtual D{};
int main (){
std::cout<<"Base="<<sizeof(Base)<<std::endl; // 8
std::cout<<"B="<<sizeof(B)<<std::endl; // 16
std::cout<<"C="<<sizeof(C)<<std::endl; // 16
std::cout<<"D="<<sizeof(D)<<std::endl; // 16
std::cout<<"E="<<sizeof(E)<<std::endl; // 24
}
显然这只是一个例子,它假设你在一个点上没有超过 1024 个对象,你可以增加这个数字但是超过 65536 你必须使用更大的 int 来存储它们,也在下面256 你可以使用 uint8_t 来存储索引。
此外,如果上述结构之一对其父结构的开销很小,您可以减少用于存储数据的数组数量,如果对象大小差异很小,您可以只存储所有数据在一个单一的结构中,并有更多的本地化内存访问。这一切都取决于您的应用程序,所以除了基准测试最适合您的情况外,我不能在这里提供更多建议。
玩得开心,享受 C++。
您可以使用以下技术避免虚拟继承:使除叶 classes 之外的所有 classes 完全抽象(无数据成员)。所有数据访问都是通过虚拟获取器进行的。
class A {
virtual int & a() = 0; // private!
// methods that access a
};
class B : public A {
virtual int & c() = 0; // private!
// methods that access b
};
class C: public A {
virtual int & c() = 0; // private!
// methods that access c
};
class D: public B, public C {
int & a() override { return a_; }
int & b() override { return b_; }
int & c() override { return c_; }
int a_, b_, c_;
};
这样,您可以 non-vir 多次继承 class 而不会复制任何数据成员(因为首先有 none)。
在示例中 D
有两次 A
,但这并不重要,因为 A
实际上 是空的。
对于典型的实现,您应该为每个最派生的 vptr class 加上每个基础的 vptr class 除了层次结构中每个级别的第一个。
当然,对于每个成员访问,您现在都有一个虚拟调用开销,但没有什么是免费的。
如果这个开销对您来说太多了,并且您仍然需要多态性,您可能需要以一种完全不涉及虚函数的 C++ 机制的方式来实现它。有很多方法可以做到这一点,但当然每种方法都有其自身的特殊缺点,因此很难推荐一种。
分析后,我发现我的程序有很大一部分内存被多虚拟继承浪费了。
这是 MCVE 来演示问题 ( http://coliru.stacked-crooked.com/a/0509965bea19f8d9 )
#include<iostream>
class Base{
public: int id=0;
};
class B : public virtual Base{
public: int fieldB=0;
public: void bFunction(){
//do something about "fieldB"
}
};
class C : public virtual B{
public: int fieldC=0;
public: void cFunction(){
//do something about "fieldC"
}
};
class D : public virtual B{
public: int fieldD=0;
};
class E : public virtual C, public virtual D{};
int main (){
std::cout<<"Base="<<sizeof(Base)<<std::endl; //4
std::cout<<"B="<<sizeof(B)<<std::endl; //16
std::cout<<"C="<<sizeof(C)<<std::endl; //32
std::cout<<"D="<<sizeof(D)<<std::endl; //32
std::cout<<"E="<<sizeof(E)<<std::endl; //56
}
希望sizeof(E)
不要超过16个字节(id
+fieldB
+fieldC
+fieldD
).
根据实验,如果是非虚拟继承,E
的大小将是24(MCVE)。
如何减小 E
的大小(通过 C++ 魔法、更改程序架构或设计模式)?
要求:-
Base,B,C,D,E
不能作为模板 class。这会对我造成循环依赖。- 我必须能够从派生 class(如果有的话)调用基 class 的 函数 ,例如
e->bFunction()
和e->cFunction()
,一如既往。
但是,如果我不能再调用e->bField
也没关系。 - 我还是想要申报方便
目前,我可以轻松地将"E inherit from C and D"
声明为class E : public virtual C, public virtual D
。
我正在考虑 CRTP,例如class E: public SomeTool<E,C,D>{}
,但不确定如何让它发挥作用。
让事情变得简单:
- 在我的例子中,每个 class 都像整体一样使用,即我永远不会在
static_cast<C*>(E*)
之类的类型之间转换对象,反之亦然。 - 允许使用宏,但不鼓励使用。
- 允许使用 Pimpl 习语。其实下面是我的白日梦
也许,我可以删除所有虚拟继承。
但是,根据所有要求,我找不到编码方法。
在 pimpl 中,如果我让E
virtual inherit fromC & D
等,上述所有要求都将得到满足,但我仍然会浪费大量内存。 :-
我正在使用 C++17。
编辑
这是对我现实生活中问题的更正确的描述。
我创建了一个包含许多组件的游戏,例如B C D E
.
它们都是通过池创建的。因此,它可以实现快速迭代。
目前,如果我从游戏引擎中查询每个 E
,我将能够调用 e->bFunction()
.
在我最严重的情况下,我在 E
-like class 中每个对象浪费 104 个字节。 (真正的层次结构更复杂)
编辑 3
让我再试一次。这是一个更有意义的class图表。
我有一个中央系统可以自动分配 hpPtr
、flyPtr
、entityId
、componentId
、typeId
。
也就是说,不用担心它们是如何初始化的。
真实情况下,恐怖钻石发生的情况很多class,这是最简单的情况。
目前,我的电话是这样的:-
auto hps = getAllComponent<HpOO>();
for(auto ele: hps){ ele->damage(); }
auto birds = getAllComponent<BirdOO>();
for(auto ele: birds ){
if(ele->someFunction()){
ele->suicidalFly();
//.... some heavy AI algorithm, etc
}
}
通过这种方法,我可以像在实体组件系统中一样享受缓存一致性,以及像在对象中一样酷炫的 ctrl+space
HpOO
、FlyableOO
和 BirdOO
智能感知- 导向风格。
一切正常 - 只是占用了太多内存。
编辑:基于问题的最新更新和一些聊天
这是在所有 classes 中维护虚拟的最紧凑的方法。
#include <iostream>
#include <vector>
using namespace std;
struct BaseFields {
int entityId{};
int16_t componentId{};
int8_t typeId{};
int16_t hpIdx;
int16_t flyPowerIdx;
};
vector<int> hp; // this will contain all the hit points, dynamically resizable, logic up to you
vector<float> flyPower; // this will contain all the fly powers, dynamically resizable, logic up to you
class BaseComponent {
public: // or protected
BaseFields data;
};
class HpOO : public virtual BaseComponent {
public:
void damage() {
hp[data.hpIdx] -= 1;
}
};
class FlyableOO : public virtual BaseComponent {
public:
void addFlyPower(float power) {
flyPower[data.hpIdx] += power;
}
};
class BirdOO : public virtual HpOO, public virtual FlyableOO {
public:
void suicidalFly() {
damage();
addFlyPower(5);
}
};
int main (){
std::cout<<"Base="<<sizeof(BaseComponent)<<std::endl; // 12
std::cout<<"C="<<sizeof(HpOO)<<std::endl; // 24
std::cout<<"D="<<sizeof(FlyableOO)<<std::endl; // 24
std::cout<<"E="<<sizeof(BirdOO)<<std::endl; // 32
}
更小的 class 大小版本删除所有虚拟 class 东西:
#include <iostream>
#include <vector>
using namespace std;
struct BaseFields {
};
vector<int> hp; // this will contain all the hit points, dynamically resizable, logic up to you
vector<float> flyPower; // this will contain all the fly powers, dynamically resizable, logic up to you
class BaseComponent {
public: // or protected
int entityId{};
int16_t componentId{};
int8_t typeId{};
int16_t hpIdx;
int16_t flyPowerIdx;
protected:
void damage() {
hp[hpIdx] -= 1;
};
void addFlyPower(float power) {
flyPower[hpIdx] += power;
}
void suicidalFly() {
damage();
addFlyPower(5);
};
};
class HpOO : public BaseComponent {
public:
using BaseComponent::damage;
};
class FlyableOO : public BaseComponent {
public:
using BaseComponent::addFlyPower;
};
class BirdOO : public BaseComponent {
public:
using BaseComponent::damage;
using BaseComponent::addFlyPower;
using BaseComponent::suicidalFly;
};
int main (){
std::cout<<"Base="<<sizeof(BaseComponent)<<std::endl; // 12
std::cout<<"C="<<sizeof(HpOO)<<std::endl; // 12
std::cout<<"D="<<sizeof(FlyableOO)<<std::endl; // 12
std::cout<<"E="<<sizeof(BirdOO)<<std::endl; // 12
// accessing example
constexpr int8_t BirdTypeId = 5;
BaseComponent x;
if( x.typeId == BirdTypeId ) {
auto y = reinterpret_cast<BirdOO *>(&x);
y->suicidalFly();
}
}
这个例子假设你的派生 classes 没有重叠的功能和不同的效果,如果你有那些你必须向你的基础 class 添加虚函数以获得 12 字节的额外开销(如果打包 class,则为 8)。
而且很可能是最小的版本仍然保持虚拟
#include <iostream>
#include <vector>
using namespace std;
struct BaseFields {
int entityId{};
int16_t componentId{};
int8_t typeId{};
int16_t hpIdx;
int16_t flyPowerIdx;
};
#define PACKED [[gnu::packed]]
vector<int> hp; // this will contain all the hit points, dynamically resizable, logic up to you
vector<float> flyPower; // this will contain all the fly powers, dynamically resizable, logic up to you
vector<BaseFields> baseFields;
class PACKED BaseComponent {
public: // or protected
int16_t baseFieldIdx{};
};
class PACKED HpOO : public virtual BaseComponent {
public:
void damage() {
hp[baseFields[baseFieldIdx].hpIdx] -= 1;
}
};
class PACKED FlyableOO : public virtual BaseComponent {
public:
void addFlyPower(float power) {
flyPower[baseFields[baseFieldIdx].hpIdx] += power;
}
};
class PACKED BirdOO : public virtual HpOO, public virtual FlyableOO {
public:
void suicidalFly() {
damage();
addFlyPower(5);
}
};
int main (){
std::cout<<"Base="<<sizeof(BaseComponent)<<std::endl; // 2
std::cout<<"C="<<sizeof(HpOO)<<std::endl; // 16 or 10
std::cout<<"D="<<sizeof(FlyableOO)<<std::endl; // 16 or 10
std::cout<<"E="<<sizeof(BirdOO)<<std::endl; // 24 or 18
}
第一个数字用于解压缩结构,第二个数字用于压缩
您还可以使用联合技巧将 hpIdx 和 flyPowerIdx 打包到 entityId 中:
union {
int32_t entityId{};
struct {
int16_t hpIdx;
int16_t flyPowerIdx;
};
};
在上面的示例中,如果不使用打包并将整个 BaseFields
结构移动到 BaseComponent
class 中,大小将保持不变。
结束编辑
虚拟继承只是在 class 的基础上增加一个指针大小,加上指针对齐(如果需要)。如果你真的需要一个虚拟的 class.
,你就无法解决这个问题您应该问自己的问题是您是否真的需要它。根据您访问此数据的方法,情况可能并非如此。
考虑到您需要虚拟继承,但需要从所有 classes 调用所有常用方法,您可以拥有一个虚拟基础 class 并且使用比 space 少一点您的原始设计如下:
class Base{
public: int id=0;
virtual ~Base();
// virtual void Function();
};
class B : public Base{
public: int fieldB=0;
// void Function() override;
};
class C : public B{
public: int fieldC=0;
};
class D : public B{
public: int fieldD=0;
};
class E : public C, public D{
};
int main (){
std::cout<<"Base="<<sizeof(Base)<<std::endl; //16
std::cout<<"B="<<sizeof(B)<<std::endl; // 16
std::cout<<"C="<<sizeof(C)<<std::endl; // 24
std::cout<<"D="<<sizeof(D)<<std::endl; // 24
std::cout<<"E="<<sizeof(E)<<std::endl; // 48
}
在缓存未命中但 CPU 仍然有能力处理结果的情况下,您可以使用 compiler-specific 指令进一步减小大小,使数据结构尽可能小(下一个例子适用于 gcc):
#include<iostream>
class [[gnu::packed]] Base {
public:
int id=0;
virtual ~Base();
virtual void bFunction() { /* do nothing */ };
virtual void cFunction() { /* do nothing */ }
};
class [[gnu::packed]] B : public Base{
public: int fieldB=0;
void bFunction() override { /* implementation */ }
};
class [[gnu::packed]] C : public B{
public: int fieldC=0;
void cFunction() override { /* implementation */ }
};
class [[gnu::packed]] D : public B{
public: int fieldD=0;
};
class [[gnu::packed]] E : public C, public D{
};
int main (){
std::cout<<"Base="<<sizeof(Base)<<std::endl; // 12
std::cout<<"B="<<sizeof(B)<<std::endl; // 16
std::cout<<"C="<<sizeof(C)<<std::endl; // 20
std::cout<<"D="<<sizeof(D)<<std::endl; // 20
std::cout<<"E="<<sizeof(E)<<std::endl; //40
}
以可能 CPU 开销的代价节省额外的 8 个字节(但如果内存是问题可能会有所帮助)。
此外,如果您确实为每个 classes 调用了一个函数,您应该只将它作为一个函数,在必要时覆盖它。
#include<iostream>
class [[gnu::packed]] Base {
public:
virtual ~Base();
virtual void specificFunction() { /* implementation for Base class */ };
int id=0;
};
class [[gnu::packed]] B : public Base{
public:
void specificFunction() override { /* implementation for B class */ }
int fieldB=0;
};
class [[gnu::packed]] C : public B{
public:
void specificFunction() override { /* implementation for C class */ }
int fieldC=0;
};
class [[gnu::packed]] D : public B{
public:
void specificFunction() override { /* implementation for D class */ }
int fieldD=0;
};
class [[gnu::packed]] E : public C, public D{
void specificFunction() override {
// implementation for E class, example:
C::specificFunction();
D::specificFunction();
}
};
这还可以让您避免在调用适当的函数之前弄清楚 class 哪个对象是什么。
此外,假设您最初的虚拟 class 继承想法最适合您的应用程序,您可以重组数据,以便更容易访问缓存目的,同时减小 classes 并同时访问您的函数:
#include <iostream>
#include <array>
using namespace std;
struct BaseFields {
int id{0};
};
struct BFields {
int fieldB;
};
struct CFields {
int fieldB;
};
struct DFields {
int fieldB;
};
array<BaseFields, 1024> baseData;
array<BaseFields, 1024> bData;
array<BaseFields, 1024> cData;
array<BaseFields, 1024> dData;
struct indexes {
uint16_t baseIndex; // index where data for Base class is stored in baseData array
uint16_t bIndex; // index where data for B class is stored in bData array
uint16_t cIndex;
uint16_t dIndex;
};
class Base{
indexes data;
};
class B : public virtual Base{
public: void bFunction(){
//do something about "fieldB"
}
};
class C : public virtual B{
public: void cFunction(){
//do something about "fieldC"
}
};
class D : public virtual B{
};
class E : public virtual C, public virtual D{};
int main (){
std::cout<<"Base="<<sizeof(Base)<<std::endl; // 8
std::cout<<"B="<<sizeof(B)<<std::endl; // 16
std::cout<<"C="<<sizeof(C)<<std::endl; // 16
std::cout<<"D="<<sizeof(D)<<std::endl; // 16
std::cout<<"E="<<sizeof(E)<<std::endl; // 24
}
显然这只是一个例子,它假设你在一个点上没有超过 1024 个对象,你可以增加这个数字但是超过 65536 你必须使用更大的 int 来存储它们,也在下面256 你可以使用 uint8_t 来存储索引。
此外,如果上述结构之一对其父结构的开销很小,您可以减少用于存储数据的数组数量,如果对象大小差异很小,您可以只存储所有数据在一个单一的结构中,并有更多的本地化内存访问。这一切都取决于您的应用程序,所以除了基准测试最适合您的情况外,我不能在这里提供更多建议。
玩得开心,享受 C++。
您可以使用以下技术避免虚拟继承:使除叶 classes 之外的所有 classes 完全抽象(无数据成员)。所有数据访问都是通过虚拟获取器进行的。
class A {
virtual int & a() = 0; // private!
// methods that access a
};
class B : public A {
virtual int & c() = 0; // private!
// methods that access b
};
class C: public A {
virtual int & c() = 0; // private!
// methods that access c
};
class D: public B, public C {
int & a() override { return a_; }
int & b() override { return b_; }
int & c() override { return c_; }
int a_, b_, c_;
};
这样,您可以 non-vir 多次继承 class 而不会复制任何数据成员(因为首先有 none)。
在示例中 D
有两次 A
,但这并不重要,因为 A
实际上 是空的。
对于典型的实现,您应该为每个最派生的 vptr class 加上每个基础的 vptr class 除了层次结构中每个级别的第一个。
当然,对于每个成员访问,您现在都有一个虚拟调用开销,但没有什么是免费的。
如果这个开销对您来说太多了,并且您仍然需要多态性,您可能需要以一种完全不涉及虚函数的 C++ 机制的方式来实现它。有很多方法可以做到这一点,但当然每种方法都有其自身的特殊缺点,因此很难推荐一种。