0

假设我有一个Squad带有Units 动态数组的类。我正在寻找一种方法来避免将#include“Unit.h”插入“Squad.h”和后者的用户。全球的想法是避免#includes 导致其他人 - 这是一件非常烦人的事情。

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

// 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!
}

(将前向声明Unit的 s 放在std::vector里面Squad会失败,因为它需要自己Squad的用户#include Unit,否则vector无法分配。)

问题一:这种做法可以接受吗?当然,我不会std::vector每次都重写(和其他人)的胆量,但是使用.cpp 中的template<typename T> T* reallocate(T*, size_t current, size_t needed)for later s 之类的算法制作标题似乎是可以忍受的。#include

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

顺便说一句,当我需要像 std::unordered_map 这样更复杂的数据结构时,我该怎么办?更换一个数组并不难,但也有像这样的“更糟糕”的情况。

4

1 回答 1

1

猜测一下,这里的一个关键问题是它vector被定义为一个有用的类,可以自动处理一些事情(比如复制)。完全自动化的代价是在定义Unit包含类 ( ) 时,值类型 ( ) 需要是完整类型Squad。为了消除这一成本,必须放弃一些自动化。有些事情仍然是自动化的,但现在程序员需要了解三(或五)规则。(完全自动化将此规则转变为零规则,这是微不足道的。)

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


这种做法可以接受吗?

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

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

  2. 您的原始指针旨在拥有它指向的内存,因此您的类需要遵循三(或五)规则。您的类需要定义(或删除)一个复制构造函数、一个复制赋值运算符和一个析构函数。所有这些定义将(可能)需要完整的声明,Unit因此您希望在您的实现文件中同时do_something_with_units定义定义(即未在头文件中内联定义)。

这是在挑剔吗?并不真地。解决这些遗漏会导致我们进入“没有保证”的领域。让我们看看您的新类定义。

// 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
};

此时,可以在头文件中实现默认构造函数(如果它初始化unitsnullptr而不是分配内存),但让我们考虑将它与其他新的放在实现文件中没什么大不了的可能性职能。如果您可以接受,则以下类定义有效,对实现文件有类似要求。

// 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
};

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

这可能就是你所缺少的。vector如果您提供用于构造、破坏和复制的显式实现,并且将这些实现放在您的实现文件中,则该方法有效。这些定义可能看起来微不足道,因为编译器在幕后做了很多工作,但是这些函数强加Unit了完整类型的要求。把他们的定义从头文件中拿出来,这个计划就奏效了。

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

于 2020-02-01T16:10:38.650 回答