1

I was looking at an explanation of the Rule of Three, and found the following code:

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

explaining things (found here: What is The Rule of Three?) I wasn't sure if I should ask the question there (since it's been a while since it was posted) or to make a new question, so I hope I don't get in trouble. I just want to see if I understood this code correctly?

From what I'm understanding here is that instead of copying the pointers (and thus having two objects point to the same memory), the object being copied to is dynamically allocating new memory which will then contain the same data that the object that was copy contains? And what is the reason that the copy assignment operator function has "delete [] name]" at the beginning? Is it because when = is used, the pointer is automatically copied and thus this new object points to the same memory as the object copied, and delete [] name deletes that memory before dynamically allocating new memory for it to point to? Actually, I'm not really sure what I'm saying so can somebody explain to me why it uses delete? And finally, what is the purpose of the if (this != &that) part?

Thanks!

Community
  • 1
  • 1
FrostyStraw
  • 1,628
  • 3
  • 25
  • 34
  • You probably need to look at the [Rule of Zero](http://stackoverflow.com/questions/14865919/wheres-the-proper-resource-handling-rule-of-zero) question and [its related article](http://flamingdangerzone.com/cxx11/2012/08/15/rule-of-zero.html). – Peter Wood Nov 16 '13 at 22:59

4 Answers4

3

The point of the delete is to free the memory previously owned by the this object for the name field. Notice that this is the assignment operator overloading, i.e:

A x,y;
x = y;

Would call the function with this == &x and that == &y.

This copies y's inner members to x while making sure to free any dynamically allocated memory previously allocated to x.name

ordahan
  • 187
  • 9
  • and this would only apply if the object being copied to previously did have its pointer member already pointing at something. If the object is newly created and assigned the value of another object, it wouldn't need to worry about that at all..did I understand that correctly? @ordahan – FrostyStraw Nov 16 '13 at 23:22
  • wait, upon further research I realized that a copy constructor is used to initialize a previously uninitialized object from some other object's date, so since it was previously uninitiliazed, it doesn't need to delete anything... – FrostyStraw Nov 17 '13 at 00:41
1

The member variable name is a pointer. It (almost always) points to a char[] that was allocated on the heap (that is, with new).

We want to copy the name of the other object to this one:

strcpy(name, that.name);

But before that we must make sure that the array which name points to is big enough to hold the new name (and a terminating '\0'), so we allocate space with new:

name = new char[strlen(that.name) + 1];

But what about the space that name previously pointed to? We don't want to just abandon it, that would be a memory leak, so we should delete it before reassigning the pointer:

delete[] name;

As for this != &that, consider what would happen if some careless person used the assignment operator from one person to itself (Alice = Alice). Work through the steps and you'll see that the name would be entirely lost.

Beta
  • 96,650
  • 16
  • 149
  • 150
1

First off, this assignment operator is a bad example! In particular it is not thread-safe! If you want to write a proper assignment operator, you write it like this:

person& operator=(person other) {
    other.swap(*this);
    return this;
}

where swap() is a member function you want anyway, simply swapping all members:

void swap(person& other) {
    std::swap(this->name, other.name);
    std::swap(this->age,  other.age);
}

With respect to your questions:

  1. The check if (this != &that) is helpfully put in by the author to indicate that the code is wrong! There are basically two cases why it is wrong:
    1. The idea of this comparison is to guard against self-assignment. If the code following this check actually depends on this check being present to be correct, it is almost certainly not exception safe (rarely, the code afterwards provides the basic guarantee; I have never seen an example where it ends up implementing the strong exception safety guarantee although there is no reason for the assignment operator to not implement the strong exception safety guarantee).
    2. If the code is just there and not strictly needed it is "optimizing" the self-assignment case. ... and impacting all cases where where the assignment is not a self-assignment which is, hopefully, the vast majority! If your code is mostly busy assigning objects to themselves, there is something more fundamental broken in the code.
  2. C++ doesn't have any garbage collection: If you allocated memory, you need to release memory (the same is, of course, true for all other resources). There are two forms to allocate memory in C++ using new:
    1. You can allocate individual objects using new T(args) or new T{args} where T is not a typedef for an array type, and args is a placeholder for the constructor arguments. Memory thus allocated needs to be release using delete ptr where ptr is the result returned from the expression above. Typically, the memory is not release explicitly but passed to an object which takes care of releasing the memory, e.g., std::unique_ptr<T>(new T(args)): the destructor of std::unique_ptr<T> will call delete as needed.
    2. You can allocate an array object, typically using new T[n] where n is the size of the array. Objects thus allocated need to be released using delete[] array where array is the result returned from the array allocation. This is rarely seen as normally you'd rather use std::string or std::vector<T> to allocate arrays. You can also use the class template mentioned above but you need to indicate that you need to release an array: std::unique_ptr<T[]>(new T[n]).
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • An assignment operator that modifies the RHS? That's... I can't seem to find the right words. Have I completely misunderstood what you wrote, or are you proposing something completely at odds with convention? – Beta Nov 17 '13 at 00:01
  • @Beta: Please note the the RHS is passed by _copy_, not by reference! That is, the assignment operator leverages the copy constructor (when the argument is passed) to create a copy the RHS, the `swap()` method to exchange the current representation with that of the copy, and the destructor (when the argument is destroy) to get rid of the original content of the LHS. Yes, it is somewhat dense but it does exactly what is wanted which includes meeting the strong exception guarantee (assuming `swap()` can't throw). – Dietmar Kühl Nov 17 '13 at 00:04
  • Ah! So I *did* completely misunderstand-- what a relief! – Beta Nov 17 '13 at 00:56
  • @Beta: I hope it isn't just a relief but also an epiphany! This assignment operator is a piece of elegance. The only fly in the ointment is that it needs to be typed out for each class needing an assignment operator! Of course, given its beauty that is kind of acceptable. – Dietmar Kühl Nov 17 '13 at 01:02
0

The reason for delete[] name; is that the class invariant is that name points to a dynamically allocated buffer sized exactly for the person's name. Notice that we're about to create a new buffer to accommodate our copy of the data stored in that.name. If we didn't delete[] what our original name was pointing to, we'd leak that memory.

As for if (this != &that), simply mentally trace what would happen if this wasn't there and someone called x = x on a person x object. First, we delete[] name. Since this == &that, this also means this->name == that.name, so we've invalidated the buffer pointed to by that.name as well. In the next step, we call strlen() on the (now invalidated) buffer, which gives Undefined Behaviour (could be e.g. a segfault).

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455