1

Let's say I have a List class:

class List
{
    private:
        class Node{
            public:
                int data;
                Node* next;
            public:
                virtual ~Node()
                {
                    if (next != NULL)
                        delete next;
                }
        };

        Node* head;
    public:
        virtual ~List()
        {
            if (head != NULL)
            {
                delete head;
            }
        }
    public:
        void AddNode(int data);
        void DeleteNode(int data);
        //....  
};

Now I want to implement a function, which takes two List reference as arguments and return a new created List:

List SumTwoList(List& list_1, List& list_2)
{
    //here I create a new List list_3 and add some elements to it based on list_1 and list_2(add operation use dynamic allocation).

    //finally I want to return this list_3.
    return list_3;
}

I am confusing here. How should I create this list_3 inside SumTwoList(). If I just make it a local variable, when function returns, the destructor of list_3 will free all the nodes added to the list and destroy the whole list. However, if I do dynamic allocation to create this list_3 and return a pointer to it, it is the user's responsibility to delete this list after using.

I don't know how to choose from these two ideas and I'm pretty sure there are some much better methods to solve this problem. Thanks ahead for your advice. :-)

Shanpei Zhou
  • 371
  • 1
  • 2
  • 17
  • 3
    You're returning a copy. The one destroyed is not the returned copy. Of course this requires having a proper copy-constructor that won't just shallow copy the pointer. – chris Apr 10 '14 at 16:32
  • 1
    @user3352668 - You need to implement a copy constructor and assignment operator. Once you do that and have confirmed that those two functions work, then your snippet of code should work. BTW, your destructor won't work. – PaulMcKenzie Apr 10 '14 at 16:33
  • @chris The copy is a copy of class List, not every node associate with this list. The head pointers of the two will point to the same node. When one is destroyed, it will delete all the nodes in the list, so the new copy of the class List does not make sense. – Shanpei Zhou Apr 10 '14 at 16:35
  • 1
    @user3352668, which is why you should have a copy constructor that doesn't shallow copy elements as chris mentioned – smac89 Apr 10 '14 at 16:37
  • I'd choose the dynamic allocation, cause then you don't have to copy a big chunk of memory when you return from the function. – adrin Apr 10 '14 at 16:39
  • 3
    @adrin, That's typically already covered by (N)RVO, and by move semantics in C++11 if (N)RVO doesn't apply. – chris Apr 10 '14 at 16:44
  • Of course it has to be said that you can do all of this without a copy-constructor and assignment operator, or even a destructor (apart from it being virtual). All you have to do is change each `Node *` to a smart pointer. – chris Apr 10 '14 at 17:00
  • @chris what will happen when I return List? The copy constructor will be called? And why do I need to overload assignment operator? – Shanpei Zhou Apr 10 '14 at 20:52
  • @user3352668, If (N)RVO can happen, it will. Else, if you're compiling with C++11 and your compiler has implemented move semantics, those will take effect and it will just be a pointer copy basically, with the destructor not doing any harm. If neither of those applies, then your object will be copied out with the copy-constructor called to construct the caller's copy. You need an assignment operator because of the [Rule of Three](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). The RoT doesn't apply when using things that implement it for you, like smart pointers. – chris Apr 10 '14 at 22:08

3 Answers3

1

One option is to return a pointer to a new List

List * SumTwoList(List& list_1, List& list_2)
{
    List * pResultList = new List;

    // Iterate over all elements of list_1 and add (append) them to pResultList
    // Iterate over all elements of list_2 and add (append) them to pResultList

    return pResultList;
}

However, it has the drawback that the caller have to remember to delete the returned object.

Rather, it might be better to design the API slightly differently where elements of list_2 are append to the existing list, for example

// Append contents of rhs to this. rhs stays as is
List::append( List const & rhs );

Callers may call it as

List list_1;
// work on list_1, such as add elements
List list_2;
// work on list_2, such as add elements

list_1.append( list_2 );

This is close to std::list::splice(), but not entirely. splice() moves the elements from rhs to this, but append() just copies the elements.

Note, your destructor is buggy, it is delete'ing only thehead, the later elements are leaked.

A sketch for destructor implementation

List::~List() {
    while( head ) {
        struct node * oldHead = head;
        head = head->next;
        delete oldHead;
    }
    head = NULL;
}
Arun
  • 19,750
  • 10
  • 51
  • 60
  • -1: `However, if I do dynamic allocation to create this list_3 and return a pointer to it, it is the user's responsibility to delete this list after using`. What does your answere provide the question doesnt state already? – Sebastian Hoffmann Apr 10 '14 at 16:43
  • @Paranaix: I know, I have mentioned that drawback in the answer itself. I have also added an alternate way to design the API. – Arun Apr 10 '14 at 16:46
  • @Arun I have modified the List class. I think now the destructor works good. Do I delete the whole list now? – Shanpei Zhou Apr 10 '14 at 16:47
  • @user3352668: Sorry, that may not work either. Please see my updated answer, I have included a sketch for the destructor. – Arun Apr 10 '14 at 16:54
  • @Arun I saw your answer. That way can definitely remove everything. However, in my method, once the List delete the first node, the first destructor of the first node will delete the second node... So I think finally every node will be deleted. Also, your method works for list. What if I have a tree? It will be hard to implement the destructor if we do not use the parent node to delete its children. I am really a beginner so I am not sure if I was right:) – Shanpei Zhou Apr 10 '14 at 17:06
  • @user3352668: [1] " the first destructor of the first node will delete the second node..." -- Did you check if this is actually happening? :-) [2] "So I think finally every node will be deleted. " -- "Think"ing is good :-), but sometimes it is good to verify! [3] For a tree, the typical option is to proceed recursively. For list, the iterative approach sketched by me can be easily translated to a recursive approach, such as `void deleteList( Node * p ) { if( p ) { Node * pNext = p->next; delete p; deleteList( pNext ); } }` – Arun Apr 10 '14 at 19:40
  • @Arun I just wrote a code to test it. The result is very strange. Only the last node cannot be deleted. For example, if I add 4 nodes to the list, the first four can be deleted. This is the same in all my test. – Shanpei Zhou Apr 10 '14 at 20:44
  • @user3352668 - Let's see your AddNode() function. – PaulMcKenzie Apr 10 '14 at 20:52
  • @PaulMcKenzie This problem has been solved. I made a so stupid mistake...http://stackoverflow.com/questions/22998753/destructor-of-list-cannot-delete-the-last-node?noredirect=1#comment35126409_22998753 – Shanpei Zhou Apr 10 '14 at 21:01
  • @user3352668 - I saw the answer. However having Node have a destructor itself is a bad idea for a linked list. If you just want to remove a single node, that destructor goes and deletes the next node just on its own. That would be incorrect. – PaulMcKenzie Apr 10 '14 at 21:20
  • @user3352668: The concept of "node destructor destructing the next node" does not sound right to me. Every node should be independent. Only the List must know that there are plurality of nodes and how they are connected. That's my view though. – Arun Apr 12 '14 at 00:21
  • @Paranaix: Does the current version of the answer satisfy you? – Arun Apr 12 '14 at 00:22
1

Add a copy constructor and assignment operator (and fix your destructor):

class List
{
    private:
        typedef struct node{
            int data;
            struct node* next;
        }NODE;

        NODE* head;
    public:

    virtual ~List()  // Please fix this, as others have mentioned!
    { }
    List(const List& rhs) 
    {
       // this needs to be implemented
    }
    List& operator = (const List& rhs) 
    {
       // this needs to be implemented
    }
    //...
};

The "this needs to be implemented" is what you need to fill in to get the copies to work.

So what does this buy you? First, you can now write functions in the form that you were attempting to do in your question. That is, you can return a List by value safely and without doing any further coding to dynamically allocate or delete a list.

List SumTwoList(List& list_1, List& list_2)
{
   List list_3;
   // do stuff to add data to list_3...
   //...
   //finally I want to return this list_3. 
   return list_3;
}

Second, say we want to copy a list to another list. That can be done easily using the "=" or by simple copy construction

If you don't want to copy, then turn copying off by making copy-ctor and assignment op private and unimplemented*, otherwise copying will be an option to the programmer using your class and the compiler will also make copies when it needs to.

In other words, if copying is implemented correctly, then a simple program like this should work:

int main()
{
   List lis1;
   // Add some nodes to lis1...
   //...
   // Assume that lis1 now has nodes...complete the copying test
   List lis2(lis1);
   List lis3;
   lis3 = lis1;
}

The program above should have no memory leaks, and no crashes, even when main() returns. If not, then copying and/or destruction is broken.

*C++11 allows you to make the copy constructor and assignment operator disabled in an easier way than with pre C++11 code by using the delete keyword when defining the functions.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
0

You will return a copy of a list. This will do what you expect if copy constructor and assignment operator of class List will be implemented.

class List
{
    private:
    //...
    public:
        List();
        List( const List& other);
        List& operator= ( const List& other);
    //...
};

Maybe better option would be however to create a constructor which takes two Lists and constructs a new one by merging them ( doing all this what SumTwoList would do):

class List
{
    private:
    //...
    public:
        List( const List& first, const List& second) { // similar to SumTwoList
        }
    //...
};
4pie0
  • 29,204
  • 9
  • 82
  • 118