提问人:Michael Sync 提问时间:9/18/2010 最后编辑:N.A.Michael Sync 更新时间:8/11/2016 访问量:17590
C++ 复制构造函数 + 指针对象
C++ Copy Constructor + Pointer Object
问:
我正在尝试学习C++中的“三巨头”。我设法为“三巨头”做了非常简单的程序。但我不确定如何使用对象指针。以下是我的第一次尝试。
当我写这篇文章时,我有一个疑问......
问题
- 这是实现默认构造函数的正确方法吗?我不确定我是否需要拥有它。但是我在另一个关于带有指针的复制构造函数的线程中发现的是,在复制构造函数中的地址之前,我需要为该指针分配空间。
- 如何在复制构造函数中分配指针变量?我在 Copy Constructor 中编写的方式可能是错误的。
- 我是否需要为 copy constructor 和 operatior= 实现相同的代码(return 除外)?
我说我需要删除析构函数中的指针是否正确?
class TreeNode { public: TreeNode(); TreeNode(const TreeNode& node); TreeNode& operator= (const TreeNode& node); ~TreeNode(); private: string data; TreeNode* left; TreeNode* right; friend class MyAnotherClass; };
实现
TreeNode::TreeNode(){
data = "";
}
TreeNode::TreeNode(const TreeNode& node){
data = node.data;
left = new TreeNode();
right = new TreeNode();
left = node.left;
right = node.right;
}
TreeNode& TreeNode::operator= (const TreeNode& node){
data = node.data;
left = node.left;
right = node.right;
return *this;
}
TreeNode::~TreeNode(){
delete left;
delete right;
}
提前致谢。
答:
这是实现默认构造函数的正确方法吗?
否,调用未分配的内容 invokes Undefined Behavior(在大多数情况下,这会导致应用程序崩溃)delete
new
在默认构造函数中设置指针。NULL
TreeNode::TreeNode(){
data = ""; //not required since data being a std::string is default initialized.
left = NULL;
right = NULL;
}
我没有看到你的其余代码有这样的问题。赋值运算符浅层复制节点,而复制构造函数深层复制节点。
根据您的要求遵循合适的方法。:-)
编辑:
不要在默认构造函数中分配指针,而是使用初始化列表
评论
我认为更好
TreeNode::TreeNode():left(NULL), right(NULL)
{
// data is already set to "" if it is std::string
}
另外,您必须在分配操作中删除指针“左”和“右”,否则会出现内存泄漏
我说我需要删除析构函数中的指针是否正确?
每当设计这样的对象时,你首先需要回答一个问题:对象是否拥有该指针指向的内存?如果是,那么显然对象的析构函数需要清理该内存,所以是的,它需要调用 delete。这似乎是您对给定代码的意图。
但是,在某些情况下,您可能希望具有引用其他对象的指针,这些对象的生存期应该由其他对象管理。在这种情况下,您不想调用 delete,因为程序的其他部分有责任这样做。此外,这更改了进入复制构造函数和赋值运算符的所有后续设计。
我将继续回答其余问题,假设您确实希望每个 TreeNode 对象都拥有左右对象的所有权。
这是实现默认构造函数的正确方法吗?
不。您需要初始化指向 NULL 的 and 指针(如果您愿意,也可以初始化为 0)。这是必需的,因为未初始化的指针可以具有任何任意值。如果您的代码默认构造一个 TreeNode,然后销毁它而不为这些指针分配任何内容,那么无论初始值是什么,都将调用 delete。因此,在此设计中,如果这些指针不指向任何内容,则必须保证它们设置为 NULL。left
right
如何在复制构造函数中分配指针变量?我在 Copy Constructor 中编写的方式可能是错误的。
该行创建一个新的 TreeNode 对象并设置为指向该对象。该行将该指针重新分配到指向 TreeNode 对象指向的任何指针。这有两个问题。left = new TreeNode();
left
left = node.left;
node.left
问题 1:现在没有任何东西指向那个新的 TreeNode。它丢失了,变成了内存泄漏,因为没有什么可以破坏它。
问题 2:现在两者都指向同一个 TreeNode。这意味着正在复制构造的对象和它从中获取值的对象都将认为它们拥有相同的 TreeNode,并且都会在析构函数中对其调用 delete。对同一对象调用 delete 两次始终是一个错误,并且会导致问题(包括可能的崩溃或内存损坏)。left
node.left
由于每个 TreeNode 都拥有其左右节点,因此最合理的做法可能是制作副本。所以你会写类似的东西:
TreeNode::TreeNode(const TreeNode& node)
: left(NULL), right(NULL)
{
data = node.data;
if(node.left)
left = new TreeNode(*node.left);
if(node.right)
right = new TreeNode(*node.right);
}
我是否需要为 copy constructor 和 operatior= 实现相同的代码(return 除外)?
几乎可以肯定。或者至少,每个代码中的代码应该具有相同的最终结果。如果副本构造和分配具有不同的效果,那将是非常令人困惑的。
编辑 - 上面的段落应该是:每个对象中的代码都应该具有相同的最终结果,因为数据是从另一个对象复制的。这通常涉及非常相似的代码。但是,赋值运算符可能需要检查是否已经分配了任何内容,以便清理它们。因此,它可能还需要注意自我分配,或者以不会导致自我分配期间发生任何不好的事情的方式编写。left
right
事实上,有一些方法可以使用另一个来实现一个,以便操作成员变量的实际代码只写在一个地方。关于 SO 的其他问题已经讨论过这个问题,例如这个问题。
评论
new TreeNode();
left
right
)
left
right
我还可以建议从库 boost 中推荐 boost::shared_ptr(如果您可以使用它),而不是简单的指针吗?它将解决您可能遇到的许多问题,例如无效指针,深度副本等。
评论
这就是我的做法:
因为您正在管理同一对象中的两个资源,所以正确地执行此操作会变得更加复杂(这就是为什么我建议永远不要在一个对象中管理多个资源)。如果您使用 copy/swap 惯用语,则复杂性仅限于复制构造函数(这对于获得强异常保证的正确性非常重要)。
TreeNode::TreeNode()
:left(NULL)
,right(NULL)
{}
/*
* Use the copy and swap idium
* Note: The parameter is by value to auto generate the copy.
* The copy uses the copy constructor above where the complex code is.
* Then the swap means that we release this tree correctly.
*/
TreeNode& TreeNode::operator= (const TreeNode node)
{
std::swap(data, node.data);
std::swap(left, node.left);
std::swap(right, node.right);
return *this;
}
TreeNode::~TreeNode()
{
delete left;
delete right;
}
现在最困难的部分:
/*
* The copy constructor is a bit harder than normal.
* This is because you want to provide the `Strong Exception Guarantee`
* If something goes wrong you definitely don't want the object to be
* in some indeterminate state.
*
* Simplified this a bit. Do the work that can generate an exception first.
* Once all this has been completed we can do the work that will not throw.
*/
TreeNode::TreeNode(const TreeNode& node)
{
// Do throwable work.
std::auto_ptr<TreeNode> nL(node.left == null ? null : new TreeNode(*node.left));
std::auto_ptr<TreeNode> nR(node.right == null ? null : new TreeNode(*node.right));
// All work that can throw has been completed.
// So now set the current node with the correct values.
data = node.data;
left = nL.release();
right = nR.release();
}
评论
上一个:C++ 中的三法则
评论
data
TreeNode
std::string
Strong Exception Guarantee