我是c++
的新手。我有一个名为QuadTree
的课程。现在QuadTree
可以包含其他4个QuadTree
的数组。它们要么都具有值,要么都将为null。因此,在我的班级头文件QuadTree.h
中,如下所示。
class QuadTree
{
public:
QuadTree();
~QuadTree();
void Clear();
private:
QuadTree nodes_[4];
};
但是此nodes_声明显示错误
'incomplete type is not allowed'.
我也尝试过
QuadTree* nodes_[4];
然后在构造函数中初始化
nodes_ = new QuadTree[4];
它给我错误'expression must be a modifiable value'.
我可以声明为列表或其他内容。但它的大小是恒定的(总是4)。所以我想使用数组。请帮助我在头文件中声明以及如何在QuadTree.cpp
文件中的构造函数中进行初始化。
答案 0 :(得分:3)
类不能包含其自身的实例。这样做将要求该类无限大,因为每个子对象将包含一个子子对象,该子子对象将包含一个子子子对象,等等。
类可以包含的内容是指向其自身类型的对象的指针。由于指针可以为null,因此可以停止无限循环,并且只包含有限数量的对象。
这意味着以下将起作用:
class QuadTree
{
public:
QuadTree() : nodes_{nullptr}
{
}
~QuadTree() { delete[] nodes_; }
// I've deleted these to make a QuadTree non-copyable.
// You'll need to implement them to do a deep copy if you want that
QuadTree(const QuadTree& other) = delete;
QuadTree& operator=(const QuadTree& other) = delete;
void Clear();
void split() {
nodes_ = new QuadTree[4] {
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ }
};
}
private:
QuadTree* nodes_;
};
像std::unique_ptr
这样的智能指针可以稍微清理一下内存管理:
class QuadTree
{
public:
// No longer have to explicitly initialize nodes_ to nullptr
// since a default-constructed unique_ptr is null
QuadTree();
// No longer need to delete[] here; unique_ptr handles that for us
~QuadTree();
// Copy constructor and assignment are now implicitly deleted since
// unique_ptr is non-copyable.
// You'll need to implement them to do a deep copy if you want that
void Clear();
void split() {
nodes_.reset(new QuadTree[4] {
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ }
});
}
private:
std::unique_ptr<QuadTree[]> nodes_;
};
为避免手动进行内存管理,可以使用std::vector<QuadTree>
代替原始的QuadTree*
。这会带来少量的内存开销,因为每个std::vector
都必须跟踪其大小,但是这样一来,您最终遇到内存损坏问题的可能性就会大大降低,因此值得这样做。这些天内存很便宜。
class QuadTree
{
public:
QuadTree();
~QuadTree();
// QuadTree is now implicitly copyable, since std::vector does a deep-copy by default
void Clear();
void split() {
nodes_ = {
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ },
{ /* TODO QuadTree args */ }
};
}
private:
std::vector<QuadTree> nodes_;
};
要避免std::vector
的内存开销,同时保持简洁的代码,可以使用静态大小实现自己的类似于矢量的类。但是,编写这样的类超出了SO答案的范围。
答案 1 :(得分:1)
class QuadTree
{
QuadTree nodes_[4];
这是不可能的,因为如果每个QuadTree包含4个QuadTree,并且所有这些QuadTree总共包含16个QuadTree子级,而所有这些QuadTree总共包含256个...直至无限个QuadTree。您不能具有无限大小的类型。这也是不可能的,因为不可能声明一个不完整类型的数组,并且该类在其定义内是不完整的(完整类上下文除外,但这在这里不适用)。
树是链接的数据结构。因此,您应该使用链接即指针来连接节点:
class QuadTree
{
QuadTree* nodes_[4];
然后在构造函数中初始化
nodes_ = new QuadTree[4];
数组不可分配。
而且,您确实不应该无条件地在QuadTree
的构造函数中创建QuadTree
的实例,否则您将无限递归,并且会溢出堆栈。
默认初始化的QuadTree
可能没有任何子代。换句话说,根据数据结构的设计,子指针应该指向null或哨兵对象。为了初始化一个指向null的指针数组,可以使用值初始化。最简单的方法是使用默认的成员初始化程序:
class QuadTree
{
QuadTree* nodes_[4] = {};
替代考虑:使用std::unique_ptr
可能比裸露的指针更好。
性能注意事项:如果树总是有0或4个子代,则最好在一个数组中创建所有子代,并最好有一个指向该数组的指针。有关更多详细信息,请参见Miles Budnek's answer。
答案 2 :(得分:0)
QuadTree* nodes_[4];
这是一个选择-包含四个指向子节点的指针的数组。 另一个选择是
QuadTree *nodes_;
是指向节点数组的指针。您必须分别跟踪长度,但是如果始终为4,就很清楚了,尽管它只允许您没有或只有4个子节点。
nodes_ = new QuadTree[4];
是后的初始化。它返回一个指向QuadTree
四个实例的数组的指针。
在前一种情况下,数组嵌入在对象中,因此您分别初始化每个成员。
首先,应将其初始化为在构造函数中包含四个空指针。你喜欢那样
QuadTree::QuadTree() : nodes_() {}
数组的初始化有点棘手,但是nullptr
是这里的默认值,因此显式初始化可以满足您的需要。
然后,当您真正需要孩子时,您只需这样做
nodes_[n] = new QuadTree();
您可能应该有一个参数化构造函数,该构造函数将初始化其他成员,因此您不会从无效对象开始。
然而,在当今时代,您真正应该使用的是
std::unique_ptr<QuadTree> nodes_[4];
smart 指针的数组,它将在您delete
父对象时删除子节点,因为在C ++中,您必须管理内存。或者,对于第二个选项,使用
std::vector<QuadTree> nodes_;
vector
是一个将管理其长度的数组-但是您必须确保QuadTree具有正确的 move 构造函数。
答案 3 :(得分:-1)
首先,是的,在这种情况下只能使用指针数组。否则,这将是一个无休止的循环。
第二,您不必为数组调用“ new”,因为在创建对象时已经分配了该数组。
因此您可以直接使用
QuadTree::QuadTree() {
for (int i = 0; i < 4; i++) {
nodes_[i] = 0;
}
}
或
QuadTree::QuadTree() {
memset(nodes_, 0, sizeof(nodes_));
}