首页 文章

C复制构造函数指针对象

提问于
浏览
8

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

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

Questions

  • 这是实现默认构造函数的正确方法吗?我不确定我是否需要它 . 但是我在另一个关于带有指针的复制构造函数的线程中发现的是我需要在复制构造函数中复制地址之前为该指针分配空间 .

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

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

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

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

Implementation

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 回答

  • 21

    我是否正确说我需要删除析构函数中的指针?

    每当设计这样的对象时,首先需要回答这个问题:对象是否拥有该指针指向的内存?如果是,那么显然对象的析构函数需要清理那个内存,所以是的,它需要调用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 . 在同一个对象上调用两次删除始终是一个错误并且会导致问题(包括可能崩溃或内存损坏) .

    由于每个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 .

  • 3

    我认为更好

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

    另外你必须删除指针在赋值操作中“左”和“右”,否则您将发生内存泄漏

  • 1

    我就是这样做的:
    因为您正在管理同一对象中的两个资源,所以正确地执行此操作变得有点复杂(这就是我推荐 never managing more than one resource in an object 的原因) . 如果你使用复制/交换习惯用法,那么复杂性仅限于复制构造函数(这对于 strong exception guarantee 来说是非常简单的) .

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

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

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

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

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

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

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

    EDIT

    而不是在默认构造函数中指定指针使用initialization list

  • 1

    我是否也可以从库boost(如果可以使用它)建议boost :: shared_ptr,而不是简单的指针?它将解决您使用无效指针,深拷贝e.t.c时可能遇到的许多问题 .

相关问题