数据复制与封装
Data duplication vs Encapsulating
我面临的问题是如何将封装和最佳内存使用结合起来。
我无法向您展示我的代码,因此无法在广泛的(我希望)示例中对其进行解释。
假设我们需要一个男性数据库。我们只想知道关于这些人的两件事:
- 男性的年龄(以出生后的小时为单位)。
- 他居住的城镇名称。
管理这些数据的方便自然的方法是创建一个对象,该对象对应于一个人并将它们存储在数组中:
class OMan1 {
public:
OMan( const int &age, const astring &t ): fAge(age), fTown(t) {}
const int& age() const: { return fAge; }
const astring& Town() const: { return fTown; }
astring FullId() const: { return fTown+fAge; }
private:
int fAge;
astring fTown;
}
OMan mans[N];
在这里,我们的 OMans 是自包含的对象,一切都很好。
除了我们克隆城镇名称数千次,并以这种方式浪费内存和执行时间。
我们可以做的改进是为城镇名称和每个 OMan 商店制作独立的数组,只为城镇的年龄和一个指向城镇数组的指针:
class OMan2 {
// same functionality as for OMan1
int fAge;
int fTownId;
astring* fTowns;
}
对象仍然是自包含的,sizeof(int) + sizeof(void*) 比 sizeof(astring) 小得多,我们赢了很多。但它仍然比 sizeof(fAge) 多 2-3 倍,我们重复 fTown 数十亿次。
内存优化对我来说至关重要,因此我所做的是只保留 fAge 和 fTownId,并将 Town() 和 FullId() 等功能从 OMan 类移动到某个类 OManDataBase:
class OMan3 {
public:
OMan( const int &age, const int &tid ): fAge(age), fTownId(tid) {}
const int& age() const: { return fAge; }
const int& TownId() const: { return fId; }
// const astring& Town() const: { return fTown; }
// astring FullId() const: { return fTown+fAge; }
private:
int fAge;
int fTownId;
}
class OManDataBase {
// constructor, destructor
const int& age( const int& i) const: { return fMans[i].TownId()]; }
const astring& Town( const int& i) const: { return fTown[fMans[i].TownId()]; }
const astring& FullId( const int& i) const: { return Town(i)+age(i); }
private:
vector<OMan3> fMans;
vector<astring> fTowns;
}
OMan3现在不是自包含的对象。例如,它不知道自己的全名。这意味着,如果我需要与一个人一起进行一些数据处理,我必须使用整个OManDataBase实例:
OBillType47 NewBillType47( const OManDataBase &db, int i ) { ... }
而不是
OBillType47 NewBillType47( const OMan &m ) { ... }
这里的封装已被破坏,代码可读性已明显降低。(我用Type47来强调我可以有很多函数,它适用于Oman-S,不能将它们全部包含在OManDataBase类中)。
我想知道是否有其他方法可以解决数据重复问题,使对象尽可能自包含?
您可以像这样创建一个泛型类:
enum class Towns {T1 = 0, T2 = 1...}
string TownsNames[] = {"T1", "T2"...}
class OManDB
{
map<Towns, OMan*> m;
// OR
map<int, Towns> m; // map by Oman ID and town
public:
void addOMan();
Oman getOManById(int id);
OMan *getOManArrByTown(Towns town);
OMan getOManTown(int omanId);
...
}
这个想法是创建一个类来保存所有记录并为您执行所有操作,例如数据库,其中您没有数据集和一组彼此分开的操作。一切都在一个地方,您可以根据需要定义操作。
这样,您可以随意更改内部表示形式,您可以返回一个 OMan 数组,您基本上可以做任何您想做的事情而不会破坏现有代码。用户将知道他/她可以获得 OMan,并且不需要知道存储其字段的确切方法。您可以将它们存储在单个 64 位字段中(例如,如果 ID 仅为 32 位,而城镇是额外的 32 位字段)。它给你自由。
更新
好吧,让我们暂时搁置封装,让我们处理性能。理想情况下,对于每一比特内存,您可以将 ID 和城镇的 ID 存储在 2 个不同的向量中,如果 2 个字段的大小不同并且编译器必须进行填充,这将产生一个重要的优势。使用 2 个不同的数组将为您提供存储数据并避免填充的最佳选择(当然您可以使用 pack...但不推荐)
对于这样的问题,有一种特殊的设计模式:蝇量级模式。最好使用它。
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 在C++程序中输入的文本文件将不起作用,除非文本被复制和粘贴
- 使用strcpy将char数组的元素复制到另一个数组
- 是否可以初始化不可复制类型的成员变量(或基类)
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- C++ Windows 驱动程序MSB3030无法复制该文件,因为它找不到
- 复制列表初始化的隐式转换的等级是多少
- 当从函数参数中的临时值调用复制构造函数时
- 有可能在Armadillo中复制MATLAB circshift方法吗
- 复制几乎为空的数组的最快方法
- 以下示例中如何避免代码复制?C++/库达
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 为什么需要复制构造函数,在哪些情况下它们非常有用
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- 为什么复制而不是移动数据元素?
- lambda[=] 上的复制值被另一个封装的 lambda[&] 阻止
- 在封装指针的类中,移动语义无意中被复制构造函数取代
- 数据复制与封装