假设我有一个带有动态数组的Squad类,我正在寻找一种方法来避免将"Unit.h“转换为”Unit.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!
}(在std::vector in Squad中提出声明的#include s失败了,因为它需要Squad的用户自己使用#include Unit,否则vector无法分配。)
问题1:这种做法可以接受吗?当然,我不打算每次都重写std::vector (和其他代码)的内核,但是用template<typename T> T* reallocate(T*, size_t current, size_t needed)等算法编写一个头部,以便在.cpp中为以后的#include提供服务似乎是可以忍受的。
第二个问题:有更好的解决方案吗?我知道pimpl的成语,但不喜欢频繁的小堆分配。
顺便说一句,当我需要一个更复杂的数据结构(如std::unordered_map )时,我该如何处理这个问题呢?替换数组并不难,但也有“更糟”的情况,如这种情况。
发布于 2020-02-01 16:10:38
据猜测,这里的一个关键问题是,vector被定义为一个有用的类,自动处理几件事情(比如复制)。完全自动化的代价是在定义包含类(Unit)时,值类型(Unit)需要是一个完整的类型。为了降低这一成本,必须放弃一些自动化。有些东西仍然是自动化的,但是现在程序员需要知道the Rule of Three (or Five)。(完全自动化将此规则转化为零规则,这很容易遵循。)
关键在于,任何解决办法都需要了解三条规则,因此它们最终不会比vector方法更好。它们一开始看起来可能更好,但不是在所有的bug都修复之后。所以,不,不要发明复杂的数据结构;坚持使用vector。
这种做法可以接受吗?
一般来说,如果做得正确,这种做法是可以接受的。然而,通常情况下,这是没有理由的。此外,您的实现是不可接受的不完整。
Squad类有一个未初始化的原始指针,允许它获取一个垃圾值。您需要初始化数据成员(特别是因为它们是private,因此只有类可以初始化它们)。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
};此时,可以在头文件中实现默认构造函数(如果它将units初始化为nullptr而不是分配内存),但让我们考虑将其与其他新函数放在实现文件中的可能性。如果您可以接受这一点,那么下面的类定义可以工作,并对实现文件有类似的要求。
// 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的完整定义。
这可能就是你错过的。如果您提供了用于构造、销毁和复制的显式实现,如果将这些实现放在实现文件中,则可以使用方法。这些定义看起来很简单,因为编译器在幕后做了很多工作,但正是这些函数强制要求Unit是一个完整的类型。将它们的定义从头文件中删除,计划就会生效。
(这就是为什么评论员对你认为vector不能工作而感到困惑的原因。在构造函数之外,vector需要Unit为完整类型的时间正好是实现需要Unit为完整类型的时间。您建议的实现是更多的工作,没有额外的好处。)
https://stackoverflow.com/questions/59995832
复制相似问题