代码之家  ›  专栏  ›  技术社区  ›  Michael Sync

C++复制构造函数+指针对象

  •  8
  • Michael Sync  · 技术社区  · 16 年前

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

    问题

    1. 如何在复制构造函数中分配指针变量?我在Copy Constructor中编写的方法可能是错误的。
    2. 我说需要删除析构函数中的指针对吗?

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

    提前谢谢。

    5 回复  |  直到 10 年前
        1
  •  22
  •   Community Mohan Dere    9 年前

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

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

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

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

    不,你需要初始化 left right 指向NULL的指针(如果愿意,也可以是0)。这是必要的,因为未初始化的指针可以有任意值。如果您的代码默认构造一个TreeNode,然后销毁它,而不给那些指针赋值,那么无论初始值是什么,都将调用delete。所以在这个设计中,如果那些指针没有指向任何东西,那么您必须保证它们被设置为NULL。

    如何在复制构造函数中分配指针变量?我在Copy Constructor中编写的方法可能是错误的。

    left = new TreeNode(); 创建新的TreeNode对象并设置 指向它。线路 left = node.left; 将指针重新指定为指向任何TreeNode对象 node.left

    问题1:现在没有任何东西指向新的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);
    }
    

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

    编辑-上面的段落应该是:每一段中的代码都应该有相同的最终结果,因为数据是从另一个对象复制的。这通常会涉及非常相似的代码。但是,赋值操作符可能需要检查是否已经为 左边 正确的 所以把它们清理干净。因此,它可能还需要注意自我分配,或者以一种在自我分配期间不会导致任何不好的事情发生的方式编写。

    事实上,有很多方法可以使用另一个来实现一个,这样操作成员变量的实际代码就只写在一个地方。其他的问题,如 this one

        2
  •  8
  •   frag    16 年前

    我觉得更好

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

        3
  •  4
  •   Loki Astari    16 年前

    我会这样做:
    因为您在同一个对象中管理两个资源,所以正确地执行此操作会变得更加复杂(这就是我建议您这样做的原因) 从不管理对象中的多个资源 强有力的例外保证 ).

    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();
    }
    
        4
  •  1
  •   Prasoon Saurav    16 年前

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

    delete 在没有使用 new 调用未定义的行为(在大多数情况下会导致应用程序崩溃)

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

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

    我不认为你的其他代码有这样的问题。赋值运算符shallow复制节点,而复制构造函数deep复制节点。

    根据您的要求采取适当的方法。:-)

    编辑 :

    使用 initialization list

        5
  •  1
  •   frag    16 年前

    我还可以建议使用boost库中的boost::shared\u ptr(如果可以使用的话),而不是简单的指针吗?它可以解决很多无效指针、深度拷贝等问题。