如何避免将 class 作为字段存储在数据结构中?

How can I avoid including a class stored as a field in a data structure?

假设我有一个 class Squad 和一个 Unit 的动态数组。我正在寻找一种方法来避免 #includeing "Unit.h" 进入 "Squad.h" 和后者的用户。全局的想法是避免 #include 导致其他的 - 一件非常烦人的事情。

我找到了一个像这样的简单解决方案:

// Squad.h
class Unit;
class Squad {
private:
  Unit*  units;
  size_t units_num;
public:
  void do_something_with_units(); // its implementation requires Unit to be a full type
};
// Squad.cpp
#include "Unit.h" // it's fine (and required) in Squad.cpp
void Squad::do_something_with_units() { // implements
}
// squad_user.h
// I want this whole file to be satisfied with only
// a forward-declaration of Unit (coming from Squad.h)
// provided it doesn't interact with Unit directly
void foo() {
  Squad squad;
  squad.do_something_with_units(); // works!
}

(将 forward-declared Units 放入 Squad 内的 std::vector 失败,因为它需要 Squad 的用户 #include Unit自己,否则vector无法分配。)

第一个问题:这种做法可以接受吗?当然,我不会每次都重写 std::vector(和其他)的内容,而是使用 template<typename T> T* reallocate(T*, size_t current, size_t needed) 等算法为以后的 #include 制作一个 header .cpp 似乎可以忍受。

问题二:有更好的解决办法吗?我知道 pimpl 习惯用法,但不喜欢它频繁的小堆分配。

顺便说一句,当我需要像std::unordered_map这样更复杂的数据结构时,我该怎么办?替换数组并不难,但有 "worse" 个像这样的案例。

据推测,这里的一个关键问题是 vector 被定义为有用的 class,自动处理几件事(如复制)。完全自动化的代价是在定义包含class(Squad)时,值类型(Unit)需要是一个完整的类型。要消除这种成本,必须放弃一些自动化。有些事情仍然是自动化的,但现在程序员需要意识到the Rule of Three (or Five)。 (完全自动化将此规则转变为零规则,遵循起来很简单。)

关键是任何变通方法都需要三法则的知识,因此它们最终不会比 vector 方法更好。它们最初看起来可能更好,但在修复所有错误之后就不是这样了。所以,不,不要发明复杂的数据结构;坚持 vector.


is such practice acceptable?

一般来说,这种做法是可以接受的如果做得正确。但是,通常情况下,这是没有必要的。此外,您的实施不完整,令人无法接受。

  1. 您的 Squad class 有一个未初始化的原始指针,允许它拾取垃圾值。您需要初始化您的数据成员(特别是因为它们是 private,因此只有 class 可以初始化它们)。

  2. 您的原始指针旨在拥有它指向的内存,因此您的 class 需要遵循 the Rule of Three (or Five)。您的 class 需要定义(或删除)一个复制构造函数、一个复制赋值运算符和一个析构函数。所有这些定义(可能)都需要 Unit 的完整声明,因此您希望实现文件中的定义与 do_something_with_units 一起(即未在头文件中内联定义)。

这是吹毛求疵吗?并不真地。解决这些遗漏会使我们进入 "not warranted" 的领域。让我们看看您的新 class 定义。

// Squad.h
class Unit;
class Squad {
private:
  Unit*  units;
  size_t units_num;
public:
  Squad();
  Squad(const Squad &);             // implementation requires Unit to be a full type
  Squad & operator=(const Squad &); // implementation requires Unit to be a full type
  ~Squad();                         // implementation requires Unit to be a full type

  void do_something_with_units();   // implementation requires Unit to be a full type
};

此时,可以在头文件中实现默认构造函数(如果它将 units 初始化为 nullptr 而不是分配内存),但让我们考虑一下它的可能性将它与其他新功能一起放在实现文件中没什么大不了的。如果你能接受,那么下面的 class 定义就可以工作,对实现文件有类似的要求。

// Squad.h
#include <vector>
class Unit;
class Squad {
private:
  std::vector<Unit> units;
public:
  Squad();                          // implementation requires Unit to be a full type
  Squad(const Squad &);             // implementation requires Unit to be a full type
  Squad & operator=(const Squad &); // implementation requires Unit to be a full type
  ~Squad();                         // implementation requires Unit to be a full type

  void do_something_with_units();   // implementation requires Unit to be a full type
};

我做了两个更改:原始指针和大小被替换为 vector,默认构造函数现在要求 Unit 是其实现的完整类型。默认构造函数的实现可能与 Squad::Squad() {} 一样简单,只要此时 ​​Unit 的完整定义可用。

这可能就是您所缺少的。如果您为构造、析构和复制提供明确的实现,并且如果您将这些实现 放入您的实现文件 ,则 vector 方法有效。这些定义可能看起来微不足道,因为编译器在幕后做了很多工作,但这些函数强加了 Unit 是完整类型的要求。将它们的定义从头文件中取出,计划就可以了。

(这就是为什么评论者对您认为 vector 不起作用的原因感到困惑。在构造函数之外,vector 需要 Unit 的时候成为一个完整的类型正是您的实现需要 Unit 成为一个完整类型的时候。您提议的实现是更多的工作,没有额外的好处。)