我正在尝试编写一个复制构造函数,它接受四叉树的节点,并复制该节点及其所有子节点及其子节点,依此类推。这就是我所拥有的:
Node(const Node & n) {
nChild=new Node(*(n.nwChild));
neChild=new Node(*(n.nChild));
eChild=new Node(*(n.seChild));
wChild=new Node(*(n.sChild));
}
我觉得最后4行有点时髦。我正在做的事情有意义吗?
答案 0 :(得分:2)
对我有意义。这是一个递归的深层副本,应该可以正常工作。但是不要忘记在析构函数中释放所有这些内存。并且在编码时要注意Quadtree
的临时副本,如果树很大,它们可能是残酷的。
此代码唯一真正的问题是距离异常安全还有几英里远。首先,它应该使用initializer list编写,这样:
Quadtree::QuadtreeNode::QuadtreeNode(const QuadtreeNode & n)
: x(n.x), y(n.y), height(n.height), width(n.width),
nwChild(new QuadtreeNode(*(n.nwChild)), // ...
构造函数体可以保持为空,除非您要做的不仅仅是初始化成员。执行此操作后,您可能需要考虑交换某些智能指针的裸指针。这将是使类异常安全的良好基础。
答案 1 :(得分:2)
您的构造函数不是异常安全的 - 假设第二个new
抛出bad_alloc
,那么第一个将如何清理?
通过将Child
字段更改为您喜欢的智能指针,您可以让自己的生活更轻松。如果你没有最喜欢的智能指针,请向自己介绍一些; - )
[编辑:刚刚注意到你没有提供数据成员定义,我只是假设它们是原始指针。抱歉,如果他们已经是聪明的指针。]
早期看起来有点奇怪 - 如果rhs上的所有四个子节点都是NULL,那么x / y /高度/宽度是不是被复制了?通常,这些成员将由初始化列表处理。如果任何的子项为NULL,而不是所有,那么你应该做些什么吗?目前,您不处理在一个方向上具有子节点但不是全部4的节点。如果它是树的属性,每个节点有4个子节点或者没有节点,那么您不需要检查所有4个,但是在复制叶节点时,需要初始化各个字段。如果一个节点可能只有一个子节点,那么就不能正确复制该节点。
除此之外,基本逻辑对我来说似乎没问题,在没有出错的意义上,它会以递归方式复制。
答案 2 :(得分:2)
除了这里的好答案(我赞成Jon的),我想介绍一种很酷的新方法来获得C ++ 0x中的异常安全性。你的编译器可能还没有实现它。但是从现在开始,我一年前都寄予厚望。但事实并非如此。
我从Jon的复制构造函数开始:
Quadtree::QuadtreeNode::QuadtreeNode(const QuadtreeNode & n)
: x(n.x), y(n.y), height(n.height), width(n.width),
nwChild(new QuadtreeNode(*(n.nwChild)), // ...
现在假设您有一个默认构造函数,如您在其中一条评论中所描述的那样:
Quadtree::QuadtreeNode::QuadtreeNode()
: x(0), y(0), height(0), width(0),
nwChild(nullptr), // ...
现在,您可以通过首先调用默认构造函数将Jon的复制构造函数转换为异常安全的构造函数! :-)而且具有讽刺意味的是,它看起来很像你的原始代码:
Quadtree::QuadtreeNode::QuadtreeNode(const QuadtreeNode & n)
: QuadtreeNode()
{
if(!(n.nwChild==NULL && n.neChild==NULL && n.seChild==NULL && n.swChild==NULL))
{
x=n.x;
y=n.y;
height=n.height;
width=n.width;
nwChild=new QuadtreeNode(*(n.nwChild));
neChild=new QuadtreeNode(*(n.neChild));
seChild=new QuadtreeNode(*(n.seChild));
swChild=new QuadtreeNode(*(n.swChild));
}
}
说明:这称为委托构造函数。并且是C ++ 0x的新功能。你可以从另一个字面上调用一个构造函数。当任何构造函数完成时,该对象被视为构造。从那时起,任何抛出的异常都将激活析构函数进行清理。因此,默认构造函数通常是以 noexcept 方式构造对象的便捷方式。然后,您可以以自然的方式构建对象,因为知道如果抛出异常将调用析构函数。
答案 3 :(得分:1)
如何分配节点?如果遇到这种情况我会看到一个问题
1 3
5
7 9
如果我要复制5
,而1
有一个指向se
的{{1}}指针,则会重新制作5
,而5
会重新制作1
{1}}等等。
答案 4 :(得分:0)
首先,我在理解反应时遇到一些困难, 因为它们指的是我没有看到的条件测试 你的原始代码。然而...
IIUC,你想做一个深层复制。有两个问题 必须考虑:如果一个(或多个)的话该怎么办 指针为null,以及如何使代码异常安全。该 对这两者最明显的解决方案是使用智能指针 对于每个指针,它执行深层复制并生成所有指针 必要的检查。设计一个好的通用copy_ptr不是 微不足道,因为它也应该处理多态指针。 但对于任何一个案例,或给定的约定,它都很简单; 如果Node不是多态的,那么shared_ptr的copy ctor就可以了 就像这样简单:
copy_ptr( T const& other )
: m_ptr( other.m_ptr == NULL ? NULL : new T( *other.m_ptr ) )
{
}
~copy_ptr()
{
delete m_ptr;
}
(使用交换习语作为对齐,并提供其他通常的 智能指针功能。)
执行此操作,您可以将初始化程序直接放入 初始化列表:
Node( Node const& other )
: nChild( other.nChild )
, neChild( other.neChild )
// ...
(没有智能指针,或者至少是一个单独的类类型 封装每个指针,你必须初始化 在初始化列表中指向null,然后分配 在try-catch块中新建指针以便删除 在发生异常时已经新建的对象。)