2

Im writing a copy assignment operator for a class I've created and I'm using a previous post as a guide: What is The Rule of Three?

Im a bit confused on one aspect of this person's explanation.

Here is their class:

class person
{
    char* name;
    int age;
};

Here is the copy assignment operator definition that I am using as a reference (there are at least 2 offered):

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

What I'm finding confusing is, why are they including the line delete[] name;?

Here is the other example they provide:

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

I immediately shied away from this one because I couldn't understand why the function checks if(this != &that) and then runs delete (delete[] name;) on an array that doesn't seem to have been generated yet. When the assignment operator is invoked, is the regular constructor called immediately before the copy assignment operator function is called? Therefore meaning that we must delete the array that was generated by the classes constructor because it can only be full of junk data?

Why can't I just write: name = that.name or

name = new char[that.name.size()];
for (int i = 0; i < that.name.size(); i++)`
{
  name[i] = that.name[i]
}

This is probably really basic and I should just implement what the post suggests but my actual use-case involves an array of structs with multiple members and so Im just having a little bit of a difficulty understanding what exactly I need to do.

I realize there are like 2.5 questions here. Any insight would be appreciated.

This is my first time implementing custom copy constructors and copy assignment operators and Im sure it will seem easy after I've done it a few times.

Thanks in advance.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
MagicGAT
  • 135
  • 7
  • 1
    If the old and new `name`would be of the same length you could reuse the `name` pointer. But if not, it must point to a new place. – Ripi2 Dec 16 '19 at 21:04
  • `person temp(that); std::swap(temp.name, name); std::swap(temp.age, age); return *this;` Using copy/swap instead of what you have in your question, that would be the entire assignment operator, providing that there is a copy constructor and destructor. – PaulMcKenzie Dec 16 '19 at 21:06
  • More on [Copy and Swap](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom). – user4581301 Dec 16 '19 at 21:20
  • Can copy() and swap() be used on arrays of `struct`s? – MagicGAT Dec 16 '19 at 21:22
  • `operator=` runs on objects that already exist. The object must have been constructed at some point earlier in the code so that you can do assignment on it. E.g. `Person p; /* ... */ p = q;` – M.M Dec 16 '19 at 21:52
  • In C++ the only difference between a `class` and a `struct` that you are ever likely to notice is `class` defaults to `private` access and `struct` defaults to `public`. Anything you can do to or with a `class` you can do with a `struct`. – user4581301 Dec 18 '19 at 02:06
  • @user4581301 I should have asked, can copy() and swap() be used on an array of `class`/`struct` s? – MagicGAT Dec 18 '19 at 02:59
  • The copy and swap idiom applies to a single object. If that object contains an array of objects, then the copy constructor allocates a new array and copies the source's array into it, object-by-object (with a helper from `` if one is fits). The swap portion only swaps the pointers so that the copy winds up with the old array and will destroy it when the copy is destroyed. – user4581301 Dec 18 '19 at 15:50

3 Answers3

7

All of this is needed to make sure that memory is managed correctly.

If I understand correctly, you need an answer to when is operator= actually called?
Well, operator= is always called when two valid objects exist. One of them is being assigned to, and second one is source of data. After this operation, both object must still remain valid.

This means than inside operator= you have this object with memory allocated for a string (which was allocated in one of the contrusctors) and that object, also with memory allocated for another string.

What I'm finding confusing is, why are they including the line delete[] name;?

We have to first clean up memory which is currently residing in this object, or else we would lose this pointer after assigning new momry to it.

When the assignment operator is invoked, is the regular constructor called immediately before the copy assignment operator function is called?

No. The object already exists, that's why operator= is called (and not a copy constructor).

Therefore meaning that we must delete the array that was generated by the classes constructor because it can only be full of junk data?

It is full of valid data. Your object was contructed, it has some data in it, and then you assign something else to this object.


Addendum: When is copy contructor called and when is operator= called? (see this question for more detailed information):

class A{};

int main()
{
    A a1; //default constructor 
    A a2 = a1; //copy-constructor, not assignment operator! New object is needed

    A a3; 
    a3 = a1; //we have valid and existing object on the left side, assignment operator is used
}
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
  • thank you very much, I had already accepted the first answer before I saw this. Both are quite good. Thank you for your time writing this. I will review the linked post as well. – MagicGAT Dec 16 '19 at 21:18
4

What I'm finding confusing is, why are they including the line delete[] name;?

When using new and new[] to manage memory, the rule is thumb is that they should have a matching delete or delete[]. Presumably, name was previously allocated with new[] (either in the constructor or by a previous assignment), so before reassigning it on the next line with name = local_name;, we need to delete[] it.

I couldn't understand why the function checks if(this != &that)

This check is a sanity check. If you assign an object to itself, then there is no need to do anything. In fact, it could cause problems because if you delete[] name; when this points to the same object as that references, then you can no longer copy that.name because its memory has been freed.

Why can't I just write: name = that.name or

This will make name in two different instances point to the same memory. In some cases, such shared memory is not only useful but desired. However, you have to be careful because if one of the instances executes delete[] on its name then name in the other instance is no longer valid.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
  • `name` was previously allocated with `new[]` in that object's constructor? That is what is confusing me, why isn't the line `delete[] that.name` ?. I could understand if we were deleting the old object's memory resources but why are we deleting the new object's memory resources, which I didn't think were even allocated yet? Unless, the object's constructor was run before the copy assignment operator code is run. – MagicGAT Dec 16 '19 at 21:07
  • @MagicGAT "name was previously allocated with new[] in that object's constructor?" Yes, either in the constructor or in a previous call to `operator=()`. – Code-Apprentice Dec 16 '19 at 21:08
  • 1
    @MagicGAT "That is what is confusing me, why isn't the line delete[] that.name ?. I could understand if we were deleting the old object's memory resources but why are we deleting the new object's memory resources" Don't think of these as the "old" vs "new" objects. They are the left and right side of the assignment operators. The object on the left presumably already has a `name` field that was allocated memory. But we are overwriting that memory and before we do, we need to free the memory that `name` points to in order to avoid a memory leak. – Code-Apprentice Dec 16 '19 at 21:10
  • ok, that makes more sense. because if are using `=` on an object, it had to have been instantiated already (even if the C++ code instantiated the object and then assigned it the value of another object in the same line) – MagicGAT Dec 16 '19 at 21:11
  • @MagicGAT Suppose you have the assignment `a = b`. Then if `operator=()` does `delete[] that.name;`, then `b.name` no longer points to a valid memory address. This will lead to surprising behavior because we don't expect the assignment to modify `b`. – Code-Apprentice Dec 16 '19 at 21:12
  • @MagicGAT "even if the C++ code instantiated the object and then assigned it the value of another object in the same line" C++ cannot instantiate an object and assign it "in the same line". We differentiate between intialization like `person a = a(my_name)` and assignment like `a = b`. These are two different operations. – Code-Apprentice Dec 16 '19 at 21:13
  • So `person a = a(my_name)` wouldn't use the copy assignment operator or copy constructor? It would use the regular constructor? – MagicGAT Dec 16 '19 at 21:15
  • @MagicGAT First of all, I got the syntax wrong. It's been a while since I've done C++ regularly. `person a(my_name);` will use the constructor that accepts a `char*` as an argument. Persumably, it allocates space for `this->name`. Similarly, `person b(a)` will call the copy constructor which will also allocate memory for `name` in `b`. No matter how you create an instance of `person`, you will allocate memory for `name`. – Code-Apprentice Dec 16 '19 at 23:24
3

Let's start from the question about this code snippet.

name = new char[that.name.size()];
for (int i = 0; i < that.name.size(); i++)`
{
  name[i] = that.name[i]
}

The data member name has the type char *. That is it is a pointer. Neither pointers nor characters pointed to by pointers have member functions like size.

So this code snippet is invalid and will not compile.

You could use such an approach like this

name = that.name;

if the data member had the type std::string.

Why can't I just write: name = that.name

In this case two objects of the class will contain pointers that point to the same memory extent. So if one object will be deleted then the pointer of other object will be invalid and using it to delete the allocated memory results in undefined behavior.

When the assignment operator is invoked, is the regular constructor called immediately before the copy assignment operator function is called?

The copy assignment operator may be applied only for already constructed object. That is an object of the class must already to exist and the used constructor shall initialize its data member name either by nullptr or by the address of allocated memory.

I immediately shied away from this one because I couldn't understand why the function checks if(this != &that)

This check allows to avoid self-assignment of an object. That is in the case when an object is assigned to itself the code of allocating and deleting memory will be redundant.

What I'm finding confusing is, why are they including the line delete[] name;?

Because the object was already constructed and its data member name can point to allocated memory that contains a string for name.

Taking all what was said into account the copy assignment operator can look like

person& operator =( const person &that )
{
    if ( this != &that )
    {
        char *local_name = new char[strlen(that.name) + 1];
        // If the above statement throws,
        // the object is still in the same state as before.
        // None of the following statements will throw an exception :)
        strcpy(local_name, that.name);
        delete[] name;
        name = local_name;
        age = that.age;
    }

    return *this;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Ok, I see now that the copy assignment operator has nothing to do with instantiation. Thank you for this clarification. – MagicGAT Dec 16 '19 at 21:27