11

I have two dummy questions which have confused me for a while. I did do some searching online and read through much c++ tutorials, however I cannot find concrete answers.

Say we have a class named Node which is a building block of a singly linked list.

class Node
{
   int data;
   Node* next;
}

Fact1: local variables(non static) will be destroyed upon the exit of the corresponding functions.

Question1: How about the situations blow:

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

Will the node n be destroyed? Or we have to use the new operator to create the node and return a pointer to a heap memory. If both ways work, which one is a better approach?

Question2: How to write a destructor for the node class? (I find some similar questions on stackOverflow, but those answers focused on the destructor for the linked list. I already got that part. What I want is exactly a destructor for a Node class).

---------------------------------------EDIT------------------------------------

Thank all who gave me suggestions or pointed out my errors. I think I got my answers. Below is a note taken by me from your answers and that really knock out my confusions.

  1. It is not a good practice to return a stack memory address from a function since it will lead to undefined behaviors.
  2. Returning a heap memory is OK but we have to take care of the destruction of the objects.
  3. An alternative will be returning an object, benefiting from the copy constructor.
Nicolas Gervais
  • 33,817
  • 13
  • 115
  • 143
user3367047
  • 323
  • 1
  • 4
  • 9
  • 2
    Does the code in question 1 even compile? – Kerrek SB Oct 10 '14 at 08:00
  • @KerrekSB, thanks for your quick response. I am reviewing c++ programming and this question just come up in my mind and I just typed the code on the fly. I just tested it and it actually won't compile. A compiled version of code are being updated. Still the same questions. – user3367047 Oct 10 '14 at 08:21
  • 1
    @user3367047 it still can't compile. The return type of the function is `Node*` but you're returning `n` which is a `Node` – eerorika Oct 10 '14 at 08:24

4 Answers4

14

Question 1

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

Your code creates a local Node instance (on the stack), then returns its address. When the function returns, the Node instance, being a local variable, is destroyed. The address the function returned now points to some memory with undefined contents, and any attempts at dereferencing this pointer will lead to undefined behavior.

In order to create a node, you actually need to call a Node constructor. How you want to return the result is relevant to how you call the constructor.

  • You can either return a pointer as you were trying to do, in which case you need to use the new operator:

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

    However, when you do this, you give func callers the responsibility to destroy the object in question. Since new and delete are symmetrical operations, it is considered better form to put them in symmetrical places in your code, e.g. like this:

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

    A better alternative altogether may be to use std::shared_ptr which gives you reference counting, like this:

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

    Using this approach, the callers do not need to manually manage each node's lifecycle. Another alternative is using std::unique_ptr instead, which only allows single object ownership.

  • Or you can return the node by value, in which case you create it locally, and then let the function return mechanisms make a copy when you return it:

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

Question 2

You can declare a destructor like this in your Node class declaration:

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

Then, you can define it like this:

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

However, it is probably better to actually let the list managed the connection between its Node instances (next field), and only let the Node class manage the lifecycle of its member data (data field)

ABCplus
  • 3,981
  • 3
  • 27
  • 43
Martin J.
  • 5,028
  • 4
  • 24
  • 41
2

You can return pointer to local object, but it will be pointed to stack memory, so results may be suprising. Look the following code:

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

You won't get "10" "20" output. Instead

Created 10
Created 20
Reading 20
Reading 1891166112

First object was destructed (when first create function call ended). Second object was created on top of destructed n1, so n1 address was equal to n2 address.

Compiler will warn you when you return stack addresses:

main.cpp: In function Node* create(int):
main.cpp:8:10: warning: address of local variable node returned [-Wreturn-local-addr]
     Node node = Node();
danbst
  • 3,363
  • 18
  • 38
1

What you did probably mean is:

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

But that would lead to undefined behavior, as the Node n would be on the stack and not under your control.

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

That would work, but you would need to free the Node* in your destructor.

If you can use c++11 features I'd probably go with an std::unique_ptr<Node>:

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

That way your Node would be freed by the destructor of the std::unique_ptr<>.

Theolodis
  • 4,977
  • 3
  • 34
  • 53
  • Great answers! std::unique_ptr<> is a good alternative. But I think it is a good practice to provide a effective destructor for other concerns. Am i right? – user3367047 Oct 10 '14 at 08:28
0

No, you don't have to use new to return an object from a function. You can return a copy of a local object.

Q1: Will the node n be destroyed?

EDIT according to the new code:

The return type of the function is Node* but you're returning n which is a Node. Your example can't compile. The local Node n will be destroyed at the end of the function, yes. As do all variables with automatic storage (non-static locals).

Answer according to the old code:

You have a Node* i.e. a pointer. You try to initialize the pointer with integer 10, but that shouldn't compile. If the code would compile, a copy of the pointer would be returned and the local pointer is destroyed at the end of the function. No Node instances have been created. Dereferencing the returned pointer would have undefined behaviour unless you're absolutely sure that you've allocated a Node instance at the memory address that you initialize the pointer to (which doesn't happen in the shown code).

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

It depends on your design. If the memory of Node's is managed by an enclosing class (for example, List) you could choose to do nothing in Node's destructor. If Node is responsible for the linked Node's memory, you'll need to allocate the instances dynamically (with new) and delete in the destructor. Manual deletion is not necessary if you use std::unique_ptr.

eerorika
  • 232,697
  • 12
  • 197
  • 326