- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
分析后,我发现我的程序有很大一部分内存被多重虚拟继承所浪费。
这是 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
不能是模板类。这会对我造成循环依赖。 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>{}
,但不确定如何使其工作。
为了让事情更简单:
static_cast<C*>(E*)
反之亦然。 E
虚拟继承自 C & D
等等,上述所有要求都会得到满足,但我仍然会浪费很多内存。 :- 我正在使用 C++17。
这是对我现实生活中问题的更正确的描述。
我创建了一个包含许多组件的游戏,例如B C D E
.
它们都是通过池创建的。因此,它可以实现快速迭代。
目前,如果我查询每个 E
从游戏引擎,我将能够调用 e->bFunction()
.
在我最严重的情况下,我在 E
中为每个对象浪费了 104 个字节。 - 类。 (真正的层次结构更复杂)
让我再试一次。下面是一个更有意义的类图。
我有一个中央系统可以分配 hpPtr
, flyPtr
, entityId
, componentId
, typeId
已经自动了。
即,不用担心它们是如何初始化的。
真实情况下,恐惧钻石在很多职业中都会出现,这是最简单的情况。
目前,我这样打电话:-
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
就像面向对象的风格。
一切正常 - 它只是占用了太多内存。
最佳答案
编辑:基于问题的最新更新和一些聊天
这是在所有类中维护虚拟的最紧凑的方法。
#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();
}
}
这个例子假设你的派生类没有重叠的功能和不同的效果,如果你有那些你必须向你的基类添加虚函数以获得 12 字节的额外开销(如果你打包类则为 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
}
第一个数字是未打包的结构,第二个是打包的
您还可以使用 union 技巧将 hpIdx 和 flyPowerIdx 打包到 entityId 中:
union {
int32_t entityId{};
struct {
int16_t hpIdx;
int16_t flyPowerIdx;
};
};
在上面的示例中,如果不使用打包并将整个 BaseFields
结构移动到 BaseComponent
类中,则大小保持不变。
结束编辑
虚拟继承只是为类增加一个指针大小,加上指针对齐(如果需要)。如果您确实需要虚拟类(class),就无法解决这个问题。
您应该问自己的问题是您是否真的需要它。根据您访问此数据的方法,情况可能并非如此。
考虑到您需要虚拟继承,但需要从所有类中调用所有常用方法,您可以拥有一个虚拟基类,并通过以下方式使用比原始设计少一点的空间:
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 仍然有能力处理结果的情况下,您可以通过使用特定于编译器的指令使数据结构尽可能小来进一步减小大小(下一个示例适用于 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 个字节(但如果内存是问题可能会有所帮助)。
此外,如果您确实为每个类调用了一个函数,您应该只将它作为一个函数,在必要时覆盖它。
#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();
}
};
这还可以让您避免在调用适当的函数之前弄清楚哪个对象是哪个类。
此外,假设您最初的虚拟类继承想法最适合您的应用程序,您可以重组数据,以便更容易访问缓存目的,同时减少类的大小并同时访问函数:
#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++。
关于c++ - 减少多虚拟继承中对象的大小(浪费),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56526705/
有WHERE 1=1有什么作用如果您在脚本(伪代码)中编写此请求: sql = "SELECT f1,f2,f3 FROM t WHERE 1=1" ++ restOfTheClause
这个问题已经有答案了: R: Convert delimited string into variables (3 个回答) 已关闭 5 年前。 我有一个包含电影数据的表,在最后一列中,它包含电影所属
假设我有一个基类: struct A{ virtual void foo() = 0; }; 然后假设我有一个这样的派生类: struct B : public virtual A{ voi
我有一个小问题,我的 << 运算符没有被正确调用。 这是我的: class SomeInterface { friend std::ostream& operator<<(std::ostrea
首先,我来自 Java 社区,并且仍然是 C++ 的学习者。 请看下面的类 第二张图片显示了类“GameObject”的子类。它还有一个 Display() 方法。 GameObject类有5个子类,
我这里遇到了一些问题。我试图让我的代码像 java 中的接口(interface)一样工作。这个类被其他 2 个继承,因为它们导致了一些问题。而且我还想知道我是否做对了,以及改进我的代码的方法。我是新
在 C++ 中,我有一个基类 A,一个子类 B。两者都有虚方法 Visit。我想在 B 中重新定义“访问”,但 B 需要访问每个 A(以及所有子类)的“访问”功能。 我有类似的东西,但它告诉我 B 无
我有一个抽象类,它是类层次结构的根。该根类有一个带有一些简单实现的方法,似乎没有必要随时随地更改该实现。 使该方法成为非虚方法很好,但是某些子类可能会意外地重新实现它。在这种情况下,虚拟 final方
在 MSDN 上,我发现在抽象方法声明中使用“virtual”修饰符是错误的。我的一位同事应该是非常有经验的开发人员,但他在他的代码中使用了这个: public abstract class Busi
C++ 虚函数表是仅用于确定调用虚函数时应该执行哪一段代码,还是在运行时有其他用途? 在维基百科上,它列出了“动态调度”作为一个原因,但没有深入了解 C++ 的更多细节...... 最佳答案 一些实现
页面大小是否恒定?更具体地说,getconf PAGE_SIZE 给出 4096,这很公平。但这可以通过程序的运行时间改变吗?或者它在整个操作系统进程生成过程中是否保持不变。 IE。 , 进程是否可能
析构函数(当然还有构造函数)和其他成员函数之间的区别在于,如果常规成员函数在派生类中具有主体,则仅执行派生类中的版本。而在析构函数的情况下,派生版本和基类版本都会被执行? 很高兴知道在析构函数(可能是
如果一个函数被定义为虚函数并且与纯虚函数相同,这究竟意味着什么? 最佳答案 来自 Wikipedia's Virtual function... In object-oriented programm
我有一个在 Jetty 下运行的应用程序,我希望该应用程序返回自引用绝对 URL(生成 RSS 提要时,因此客户端必须能够在没有“当前 URL”上下文的情况下工作)。 问题是我事先不知道应用程序将部署
如何在两个virtualtreeview之间复制以复制所有列,而不仅仅是第一列? 复制前: 复制后: 最佳答案 树控件不保存任何数据。它不包含要显示的列数据,因此无法复制它。而是,当树控件想要显示任何
我已将 ShowHint 设置为 true 并将 HintMode 设置为 hmToolTip,但是当我将光标悬停在控件上时,我的 OnGetHint() 事件处理程序甚至没有断点。 知道我做错了什么
我的 friend 正在 Delphi 中使用 VirtualTreeView 工作,并且遇到了下一个问题:他有两列,第一列的每一行都有数据和子项。是否可以不更改第一列宽度来设置最大子列宽度? 图例:
我在我的 Virtual TreeView Component 中使用 TVirtualStringTree ( Delphi project 的一部分)我想创建一个 View ,其中 2 列可以有可
我想遍历 VirtualTreeView 的所有根并将其删除。 我不想清除它。 我收到此代码的访问冲突: var Node : PVirtualNode; begin if VirtualStri
我有一个可以输出表单的 PHP 文件。我想在服务器端调用这个 PHP 文件(当前使用“include”),填写并提交。 这样更好,因此我不必干预实际的 PHP 表单,只需处理表示层,以便数据可以被它自
我是一名优秀的程序员,十分优秀!