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

Mic*_*ync 8 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;
    };
    
    Run Code Online (Sandbox Code Playgroud)

履行

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;
}
Run Code Online (Sandbox Code Playgroud)

提前致谢.

The*_*ish 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,并且在它们的析构函数中都会调用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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


小智 8

我认为更好

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

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

  • 我提到初始化列表的+1. (4认同)