C ++ Copy构造函数+指针对象

时间:2010-09-18 05:07:58

标签: c++ rule-of-three

我正在努力学习C ++中的“三巨头”..我设法为“三巨头”做了非常简单的程序..但我不知道如何使用对象指针..以下是我的第一个尝试。

当我写这篇文章时,我有一个疑问......

问题

  1. 这是实现默认构造函数的正确方法吗?我不确定我是否需要它。但是我在另一个关于带有指针的复制构造函数的线程中发现的是我需要在复制构造函数中复制地址之前为该指针分配空间。
  2. 如何在复制构造函数中指定指针变量?我在Copy Constructor中编写的方式可能有误。
  3. 我是否需要为复制构造函数和operatior实现相同的代码(返回除外)=?
  4. 我说我需要删除析构函数中的指针吗?

    class TreeNode
    {
    public:  
       TreeNode(); 
       TreeNode(const TreeNode& node);
       TreeNode& operator= (const TreeNode& node);
       ~TreeNode();
    private:
       string data;
       TreeNode* left;
       TreeNode* right;
       friend class MyAnotherClass;
    };
    
  5. 实施

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

    提前致谢。

5 个答案:

答案 0 :(得分:22)

  

我说我需要删除析构函数中的指针吗?

每当设计这样的对象时,首先需要回答这个问题:对象是否拥有该指针指向的内存?如果是,那么显然对象的析构函数需要清理那个内存,所以是的,它需要调用delete。这似乎是你对给定代码的意图。

但是在某些情况下,您可能希望使用指向其他对象的指针,这些对象的生命周期应由其他对象管理。在这种情况下,您不希望调用delete,因为这是程序的其他部分的职责。此外,这会改变进入复制构造函数和赋值运算符的所有后续设计。

我假设您确实希望每个TreeNode对象拥有左右对象的所有权,我将继续回答其余问题。

  

这是实现默认构造函数的正确方法吗?

没有。您需要将leftright指针初始化为NULL(如果您愿意,则为0)。这是必要的,因为未初始化的指针可以具有任意值。如果你的代码默认构造一个TreeNode,然后在没有为这些指针分配任何内容的情况下销毁它,那么就会在初始值上调用delete。所以在这个设计中,如果那些指针没有指向任何东西,那么你必须保证它们被设置为NULL。

  

如何在复制构造函数中指定指针变量?我在Copy Constructor中编写的方式可能有误。

left = new TreeNode();创建一个新的TreeNode对象,并将left设置为指向它。行left = node.left;重新指定该指针指向TreeNode对象node.left指向的任何内容。这有两个问题。

问题1:现在没有任何内容指向新的TreeNode。它丢失了,变成了内存泄漏,因为没有东西可以破坏它。

问题2:现在leftnode.left最终都指向同一个TreeNode。这意味着正在复制构造的对象以及它从中获取值的对象都认为它们拥有相同的TreeNode,并且在它们的析构函数中都会调用delete。在同一个对象上调用delete两次总是一个bug,会导致问题(包括可能崩溃或内存损坏)。

由于每个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);
}
  

我是否需要为复制构造函数和operatior实现相同的代码(返回除外)=?

几乎可以肯定。或者至少,每个代码中的代码应该具有相同的最终结果。如果复制构造和作业具有不同的效果,那将会非常混乱。

编辑 - 上面的段落应该是:每个中的代码应该具有相同的最终结果,因为数据是从另一个对象复制的。这通常涉及非常相似的代码。但是,赋值运算符可能需要检查是否已将任何内容分配给leftright,然后清除它们。因此,它可能还需要注意自我分配,或者以不会在自我分配期间发生任何不良事件的方式编写。

实际上,有一些方法可以使用另一个实现一个,以便操作成员变量的实际代码只写在一个地方。关于SO的其他问题已经讨论过,例如this one

答案 1 :(得分:8)

更好,我认为

 TreeNode::TreeNode():left(NULL), right(NULL)
 {
   // data is already set to "" if it is std::string
 }

另外你必须在赋值操作中删除指针'left'和'right',否则你会有内存泄漏

答案 2 :(得分:3)

我就是这样做的:
因为您正在管理同一对象中的两个资源,所以正确地执行此操作变得有点复杂(这就是为什么我建议永远不管理对象中的多个资源)。如果您使用复制/交换习惯用法,则复杂性仅限于复制构造函数(对于强大的异常保证而言,这是非常简单的。)

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();
}

答案 3 :(得分:1)

  

这是实现默认构造函数的正确方法吗?

不,对未使用delete分配的内容调用new会调用未定义的行为(在大多数情况下会导致应用程序崩溃)

在默认构造函数中将指针设置为NULL

TreeNode::TreeNode(){
  data = "";   //not required since data being a std::string is default initialized.
  left = NULL;
  right = NULL;   
}

我没有看到其余代码出现此类问题。您的赋值运算符浅表复制节点,而复制构造函数则深层复制它们。

根据您的要求采用合适的方法。 : - )

编辑

使用initialization list

而不是在默认构造函数中指定指针

答案 4 :(得分:1)

我是否也可以建议来自库boost的boost :: shared_ptr(如果你可以使用它),而不是简单的指针?它将解决您使用无效指针,深拷贝e.t.c。

时可能遇到的许多问题