【问题标题】:C++:When creating a new objects inside a function and returning it as result, must I use the new operator to create the object?C++:在函数内创建新对象并将其作为结果返回时,我必须使用 new 运算符来创建对象吗?
【发布时间】:2014-12-05 08:02:57
【问题描述】:

我有两个愚蠢的问题让我困惑了一段时间。我确实在网上做了一些搜索并阅读了很多 c++ 教程,但是我找不到具体的答案。

假设我们有一个名为 Node 的类,它是单链表的构建块。

class Node
{
   int data;
   Node* next;
}

事实1:局部变量(非静态)将在相应函数退出时被销毁。

问题1:情况如何:

Node* func()
{ 
    Node n; 
    Node* ptr=&n; 
    return n;
}

节点n会被销毁吗?或者我们必须使用 new 运算符来创建节点并返回指向堆内存的指针。如果两种方法都有效,那么哪种方法更好?

问题2:如何为节点类写析构函数? (我在 stackOverflow 上发现了一些类似的问题,但这些答案集中在链表的析构函数上。我已经得到了那部分。我想要的正是 Node 类的析构函数)。

---------------------------------------编辑-------- -----------------------------

感谢所有给我建议或指出我错误的人。我想我得到了答案。下面是我从你的回答中做的一个笔记,它确实消除了我的困惑。

  1. 从函数返回堆栈内存地址不是一个好习惯,因为它会导致未定义的行为。
  2. 返回堆内存是可以的,但我们必须注意对象的销毁。
  3. 另一种方法是返回一个对象,受益于复制构造函数。

【问题讨论】:

  • 问题 1 中的代码还能编译吗?
  • @KerrekSB,感谢您的快速回复。我正在审查 c++ 编程,这个问题刚刚出现在我的脑海中,我只是在运行中输入了代码。我只是测试了它,它实际上不会编译。代码的编译版本正在更新。还是同样的问题。
  • @user3367047 它仍然无法编译。该函数的返回类型是Node*,但您返回的是n,它是Node

标签: c++ constructor destructor


【解决方案1】:

问题 1

Node* func() { Node n; Node* ptr=&n; return n;}

您的代码创建一个本地 Node 实例(在堆栈上),然后返回其地址。当函数返回时,作为局部变量的Node 实例被销毁。函数返回的地址现在指向一些内容未定义的内存,任何取消引用此指针的尝试都将导致未定义的行为。

为了创建一个节点,你实际上需要调用一个Node 构造函数。您希望如何返回结果与您如何调用构造函数有关。

  • 您可以像尝试那样返回一个指针,在这种情况下,您需要使用new 运算符:

      Node* func() { 
        Node* n = new Node(10); 
        return n;
      }
    

    但是,当你这样做时,你让func 调用者负责销毁相关对象。由于newdelete 是对称操作,因此将它们放在代码中的对称位置被认为是更好的形式,例如像这样:

      void cnuf(Node* p) { 
        delete p; 
      }
    

    更好的选择可能是使用std::shared_ptr,它可以为您提供引用计数,如下所示:

      std::shared_ptr<Node> func() {
        return std::make_shared<Node>(10);
      }
    

    使用这种方法,调用者不需要手动管理每个节点的生命周期。另一种选择是改用std::unique_ptr,它只允许单一对象所有权。

  • 或者你可以按值返回节点,在这种情况下你在本地创建它,然后在你返回的时候让函数返回机制做一个副本:

      Node func() { 
        Node n(10); 
        return n;
      }
    

问题 2

您可以在 Node 类声明中声明这样的析构函数:

class Node {
  ...
  ~Node();
}

然后,你可以这样定义:

Node::~Node() {
  ...
}

但是,实际上让列表管理其Node 实例(next 字段)之间的连接可能更好,并且只让Node 类管理其成员数据的生命周期(data 字段)

【讨论】:

    【解决方案2】:

    您可以返回指向本地对象的指针,但它会指向堆栈内存,因此结果可能会令人惊讶。看下面的代码:

    #include <iostream>
    
    using namespace std;
    
    class Node { public: int n; };
    
    Node* create(int n) {
        Node node = Node();
        node.n = n;
        cout << "Created " << node.n << endl;
        return &node;
    }
    
    int main() {
       Node* n1 = create(10);
       Node* n2 = create(20);
       cout << "Reading " << n1->n << endl;
       cout << "Reading " << n2->n << endl;
       return 0;
    }
    

    你不会得到“10”“20”的输出。而是

    Created 10
    Created 20
    Reading 20
    Reading 1891166112
    

    第一个对象被破坏(当第一个 create 函数调用结束时)。第二个对象是在被破坏的n1 之上创建的,所以n1 地址等于n2 地址。

    当您返回堆栈地址时,编译器会警告您:

    main.cpp: In function Node* create(int):
    main.cpp:8:10: warning: address of local variable node returned [-Wreturn-local-addr]
         Node node = Node();
    

    【讨论】:

    • 实际实验的好答案。这很有帮助。谢谢!
    【解决方案3】:

    你的意思可能是:

    Node* func()
    { 
        Node n(10); 
        return &n;
    }
    

    但这会导致未定义的行为,因为Node n 将在堆栈上,不受您的控制。

    Node* func()
    { 
        Node* n = new Node(10); 
        return n;
    }
    

    这可行,但您需要在析构函数中释放 Node*

    如果您可以使用 c++11 功能,我可能会选择std::unique_ptr&lt;Node&gt;

    class Node
    {
       int data;
       std::unique_ptr<Node> next;
    }
    std::unique_ptr<Node> func()
    { 
        std::unique_ptr<Node> n(new Node(10)); 
        return n;
    }
    

    这样你的节点就会被std::unique_ptr&lt;&gt;的析构函数释放。

    【讨论】:

    • 很好的答案! std::unique_ptr 是一个不错的选择。但我认为为其他问题提供有效的析构函数是一种很好的做法。我说的对吗?
    【解决方案4】:

    不,您不必使用new 从函数返回对象。您可以返回本地对象的副本。

    Q1:Will the node n be destroyed?

    根据新代码编辑:

    函数的返回类型是 Node*,但您返回的是一个 Node。您的示例无法编译。本地的Node n 会在函数结束时销毁,是的。与所有具有自动存储(非静态局部变量)的变量一样。

    按照旧代码回答:

    你有一个Node*,即一个指针。您尝试使用整数 10 初始化指针,但这不应该编译。如果代码可以编译,则将返回指针的副本,并在函数结束时销毁本地指针。没有创建 Node 实例。取消引用返回的指针将具有未定义的行为,除非您绝对确定您已在初始化指针的内存地址分配了一个 Node 实例(这在所示代码中不会发生)。

    Q2:How to write a destructor for the node class?

    这取决于您的设计。如果Node 的内存由封闭类(例如List)管理,您可以选择在Node 的析构函数中什么都不做。如果Node 负责链接Node 的内存,则需要在析构函数中动态分配实例(使用new)和delete。如果您使用std::unique_ptr,则无需手动删除。

    【讨论】:

    • 很好的答案。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-16
    • 1970-01-01
    • 2018-06-09
    相关资源
    最近更新 更多