0

Title kind of hits the mark.

    // copy constructor
IntList::IntList(const IntList& source){
    Node* currentNode = source.first;
    first = nullptr;
    while(currentNode){
        append(currentNode->info);
        currentNode = currentNode->next;
    }
}

//Assignment operator should copy the list from the source
//to this list, deleting/replacing any existing nodes
IntList& IntList::operator=(const IntList& source){
    this -> ~IntList();
    this = new IntList(source);
    return *this;
}

Error: intlist.cpp:26:30: error: lvalue required as left operand of assignment this = new IntList(source);

The destructor properly deletes each node in the linked list. Technically "this" would be whatever linked list was passed in with the assignment operator. Isn't that treated as an object that can be set?

  • 1
    `this -> ~IntList();` -- This is wrong. This should not be done in your class whatsoever. A simple [copy/swap](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) of the members is all you need to do, without any calls to `new`. – PaulMcKenzie Apr 30 '21 at 03:00
  • related/dupe: https://stackoverflow.com/questions/13476879/how-can-you-assign-a-value-to-the-pointer-this-in-c – NathanOliver Apr 30 '21 at 03:01
  • 2
    At a purely mechanical level, `this` is not modifiable. `this = whatever;` is illegal. – Pete Becker Apr 30 '21 at 03:11

3 Answers3

4

The short answer is that you can't assign this. (this is the pointer to the current object it cannot be "relocated", it is a "const" pointer for all purposes).

What you mean is something like:

    new(this) IntList(source);

Which is called "placement" new:

But you don't need to learn about this because all this is bad anyway. Keep reading...


Yours is an old and bad design pattern to implement assignment as destruction + construction.

These days, unless there is a better shortcut (that depends on you specific design) we implement swap (also useful) and then operator= from copy(construct)+swap.

(Besides any explict call to the destructor smells bad.)

IntList& IntList::operator=(IntList const& source){
    IntList tmp(source)
    this->swap(tmp);
    return *this;
}

The main point is not only more elegant code, ultimately a side effect is that you are writing exception safe code by default. (think of what happens if the new object cannot be constructed in your original code: the *this object is left in an invalid "destructed" state.)

See the series of talks: https://www.youtube.com/watch?v=W7fIy_54y-w


This is really advanced now and it gets weird (as discussed with @PaulMcKenzie). It turns out that (I think for some quirks only in C++14), that you can omit the temporary and transfer that into the argument itself passed by value:

IntList& IntList::operator=(IntList source) noexcept{
    return swap(source);
}

Although it can be obscure at first.

  1. It is less code
  2. it is noexcept: it can't fail!. The operation call can fail but not inside the function
  3. this provides move assignment if there a move constructor, so you don't need to write a separate move assignment (even less code).

(Incidentally, I think this shows why member swap should return *this)

This opens an intriguing question: if I have copy constructor and swap, shouldn't the compiler implement operator= automatically for me? (not the old-C operator= member-by-member, but one that knows about the semantics of the class implemented like shown). IntList& operator=(IntList) = default;


Finally, don't miss the forest for the tree.

You wanted to implement assignment without writing new code, assuming that your copy constructor was good and that is all you got.

However this is still not always optimal!

For containers copy construction usually involves a) (unconditional) allocation and b) a sequence of "uninitialized copies" (ultimately a form of placement new). Both a) and b) are slower than A) no-allocation and B) a sequence of assignment respectively.

This means that, if for some reason you know that you don't need to allocate (e.g. because the source "fits" in your allocated memory already), and that you can assign element by element (faster than new-place element by element) you might be in good shape to get a more optimal solution by writing a custom operator= that takes into account all this.

alfC
  • 14,261
  • 4
  • 67
  • 118
  • Thank you for this... Wouldn't it still be necessary to call the destructor on the old IntList just in case it was stored in the heap... ie: IntList listA = new IntList(parameters); IntList listB(parameters); listA = listB; If you don't call the destructor on 'this', aren't you going to have a memory leak if you're passing in anything from the heap? – Justin Kasowski Apr 30 '21 at 03:21
  • @JustinKasowski `IntList listA = new IntList(parameters); IntList listB(parameters); listA = listB;`. This is totally wrong. You are assigning an object to a pointer. You should learn about how pointers and dynamic memory allocations work, at best by reading some [good book about C++ for beginners](https://stackoverflow.com/q/388242/580083). – Daniel Langr Apr 30 '21 at 03:26
  • @JustinKasowski -- If you look at the code, where would there be a memory leak? No calls to `new` were done, everything is value-based. The destructor of `tmp` is called when `tmp` goes out of scope automatically, `delete[]`-ing the old memory. That's the purpose of the destructor. – PaulMcKenzie Apr 30 '21 at 03:31
  • The original IntList that's being overloaded could potentially have been made with 'new'. *edit* I get it now, you swap whatever IntList you're passing in with tmp and then tmp is cleared. Don't know why I blanked on that – Justin Kasowski Apr 30 '21 at 03:38
  • 1
    @JustinKasowski -- Then that's the coder's "fault" to have used `new` to create it. It has absolutely nothing to do with the internals of `IntList` itself, which is what your question and the answer given deals with. – PaulMcKenzie Apr 30 '21 at 03:40
  • This is for an intro to programming course and it specifically states in the prompt to delete the old IntList – Justin Kasowski Apr 30 '21 at 03:42
  • @JustinKasowski, that's fine, you might be asked to "delete the old thing" but you never do that from inside the class. – alfC Apr 30 '21 at 04:08
  • @JustinKasowski -- The old `IntList` *is* being deleted. It is being deleted by transferring the contents to another temporary object that will die off with that data. I think the issue is that you never thought of doing it this way, where the object says "temporary object, give me what you have, and I give you what I have.". If you need further proof, debug the copy/swap way of doing things, and you magically see the destructor being called, thus issuing the `delete[]` call you're not seeing. – PaulMcKenzie Apr 30 '21 at 07:21
  • Yes, thank you @PaulMcKenzie I blanked for a second but I understood what the swapping would accomplish. I appreciate the help! – Justin Kasowski May 09 '21 at 05:29
0

If your copy constructor and destructor for IntList are working properly, the easiest way to implement the assignment operator is to use the copy/swap idiom:

#include <algorithm>
//...
IntList& IntList::operator=(const IntList& source)
{
    if (&source != this)
    {
       IntList temp(source);
       std::swap(temp.first, first);
       // add swaps for each member
       //...
    }  // <-- This is where the old contents will be destroyed, 
       // when `temp` goes out of scope.  No need for this->~IntList();
    return *this;
}

In your code, doing this:

this->~IntList();

is not necessary, and probably is wrong anyway. Code like that is reserved for usage with placement-new, and your code doesn't show any usage of placement-new.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • The conditional check is semantically unnecessary, and a dubious optimization. Unless you like to write `my_list = my_list;` very often or your algorithms are designed to do unnecessary indirect self assignments. – alfC Apr 30 '21 at 03:14
  • I know that the self check is not necessary. But I have no idea if creating `IntList` is expensive or not, since the OP didn't post its details. There is no harm in making this check. – PaulMcKenzie Apr 30 '21 at 03:16
  • Yes, the harm is to do a check every time, just because every blue moon you might do an ill conceived self-assignment. I stand by this regardless of whether `IntList` is expensive to construct/copy or not. – alfC Apr 30 '21 at 03:24
  • 1
    Then if this is your concern, then assignment operator should have been: `IntList& IntList::operator=(IntList source)` with no self-check. – PaulMcKenzie Apr 30 '21 at 03:27
  • yes, I do agree. It can also be `noexcept` in that case ;) – alfC Apr 30 '21 at 04:03
-3

A common mistake is to call the assignment operator from the copy constructor. Take the Array class for example. Inside the copy you might think it's simple to write