3

I'm trying to understand how the copy assignment constructor works in c++. I've only worked with java so i'm really out of my waters here. I've read and seen that it's a good practice to return a reference but i don't get how i should do that. I wrote this small program to test the concept:

main.cpp:

#include <iostream>
#include "test.h"

using namespace std;

int main() {
    Test t1,t2;
    t1.setAge(10);
    t1.setId('a');
    t2.setAge(20);
    t2.setId('b');

    cout << "T2 (before) : " << t2.getAge() << t2.getID() << "\n";

    t2 = t1; // calls assignment operator, same as t2.operator=(t1)

    cout << "T2 (assignment operator called) : " << t2.getAge() << t2.getID() << "\n";

    Test t3 = t1; // copy constr, same as Test t3(t1)

    cout << "T3 (copy constructor using T1) : " << t3.getAge() << t3.getID() << "\n";

    return 1;
}

test.h:

class Test {
    int age;
    char id;

    public:
        Test(){};
        Test(const Test& t); // copy
        Test& operator=(const Test& obj); // copy assign
        ~Test();
        void setAge(int a);
        void setId(char i);
        int getAge() const {return age;};
        char getID() const {return id;};
};

test.cpp:

#include "test.h"

void Test::setAge(int a) {
    age = a;
}

void Test::setId(char i) {
    id = i;
}

Test::Test(const Test& t) {
    age = t.getAge();
    id = t.getID();
}

Test& Test::operator=(const Test& t) {

}

Test::~Test() {};

I can't seem to understand what i should be putting inside operator=(). I've seen people returning *this but that from what i read is just a reference to the object itself (on the left of the =), right? I then thought about returning a copy of the const Test& t object but then there would be no point to using this constructor right? What do i return and why?

  • 7
    `Test::operator=(const Test&)` is not a constructor. It's a copy assignment operator. A constructor creates a new object; an assignment operator modifies an existing object. – Pete Becker Jun 07 '19 at 21:14
  • @PeteBecker i figured that, it's just that our college teacher calls it `copy assignment constructor` all the time. Should i not call it that even if i know what it really is? – Stelios Papamichail Jun 07 '19 at 21:16
  • 4
    @SteliosPapamichail Seems your teacher is not too familiar with C++ concepts. – john Jun 07 '19 at 21:17
  • 2
    Sidenote: Give the [Copy and Swap Idiom](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) a read-through. Not only does it make for a bulletproof (assuming the copy constructor is correct) assignment operator, but it's simple to write and very hard to get wrong. It's also a bit heavyweight, so it's not always the right solution, but it's almost always a great place to start and stay until profiling proves otherwise. – user4581301 Jun 07 '19 at 21:17
  • @user4581301 thank you very much, i'll give this a read now – Stelios Papamichail Jun 07 '19 at 21:20
  • 2
    See also: https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading – Richard Critten Jun 07 '19 at 21:26
  • 2
    `Test t3 = t1;` and `Test t3(t1);` are not really the same. E.g., if the copy constructor is marked as `explicit`, only the second alternative will compile. – Evg Jun 07 '19 at 21:27
  • @Evg i guess our teacher really needs to explain this stuff a bit more. We've not even touched `explicit`. Good to know, thank you! – Stelios Papamichail Jun 07 '19 at 21:28
  • 2
    To be picky, there is no such ing as a "copy assignment constructor" - there is a a "copy assignment operator" –  Jun 07 '19 at 21:35
  • @NeilButterworth got it, i'll call it what it is then! Thanks everyone – Stelios Papamichail Jun 07 '19 at 21:37

3 Answers3

5

I've read and seen that it's a good practice to return a reference but i don't get how i should do that.

How

Add

return *this;

as the last line in the function.

Test& Test::operator=(const Test& t) {
   ...
   return *this;
}

Why

As to the question of why you should return *this, the answer is that it is idiomatic.

For fundamental types, you can use things like:

int i;
i = 10;
i = someFunction();

You can use them in a chain operation.

int j = i = someFunction();

You can use them in a conditional.

if ( (i = someFunction()) != 0 ) { /* Do something */ }

You can use them in a function call.

foo((i = someFunction());

They work because i = ... evaluates to a reference to i. It's idiomatic to keep that semantic even for user defined types. You should be able to use:

Test a;
Test b;

b = a = someFunctionThatReturnsTest();

if ( (a = omeFunctionThatReturnsTest()).getAge() > 20 ) { /* Do something */ }

But Then

More importantly, you should avoid writing a destructor, a copy constructor, and a copy assignment operator for the posted class. The compiler created implementations will be sufficient for Test.

R Sahu
  • 196,807
  • 13
  • 136
  • 247
  • 3
    "*I've seen people returning `*this` but that from what i read is just a reference to the object itself (on the left of the =), right?*" It sounds like OP knows that this is something they should do. Maybe you could add some explanation for **why** this is correct (as that seems to be what they're missing). – scohe001 Jun 07 '19 at 21:16
  • @scohe001 you're right, i actually tried that and run the program but `t2`'s values didn't change. Am i missing something here? – Stelios Papamichail Jun 07 '19 at 21:17
  • 1
    @SteliosPapamichail you still need to do the actual work of copying the values in `t` into `this`! If you just return without doing anything, then of course nothing will happen :p – scohe001 Jun 07 '19 at 21:18
  • @scohe001 that makes sense :P . So something similar to `age = t.getAge()` etc. and then `return *this;` ? – Stelios Papamichail Jun 07 '19 at 21:19
  • 1
    @SteliosPapamichail yessir. It should look very similar to the constructor (with some additional work of throwing away any values you already have, but since this class is simple, you don't have anything that needs that). And then you'll be able to use that assignment operator in your constructor instead of duplicating the work! You can change the constructor to just be `*this = t;` – scohe001 Jun 07 '19 at 21:21
  • @scohe001 wow that's actually super useful and efficient. Thanks a ton this really helped me. Now i get what i'm supposed to do. Would you mind drafting up an answer so i can select it? – Stelios Papamichail Jun 07 '19 at 21:23
  • 3
    @scohe001 I don't know if it's a good idea to suggest writing the copy constructor and then calling the assignment operator within the copy constructor. It would be better for the copy constructor to live on its own, and then simply implement the assignment operator in terms of the copy constructor using copy / swap. – PaulMcKenzie Jun 07 '19 at 21:29
  • @PaulMcKenzie what problems could occur from doing this sir? – Stelios Papamichail Jun 07 '19 at 21:31
  • 1
    @PaulMcKenzie I agree. Especially in a more complicated class, calling the operator may not even be an option. I've detailed that field initialization lists are preferred in my answer. Thanks for the catch! – scohe001 Jun 07 '19 at 21:31
  • 3
    @SteliosPapamichail, may I suggest https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three and https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom – R Sahu Jun 07 '19 at 21:32
  • 3
    @Stelios -- If resources need to be allocted, the assignment operator has an additional step of deallocating the current resources, while the copy constructor does not do this. Moving all the code from the copy constructor to the assignment operator sort of confuses this difference. You need to make sure that the copy constructor has "null" resources, else the assignment operator will attempt to deallocate junk. That's the danger IMO. I have seen many times a programmer forget to null the pointers to the resources out, and then call the assignment operator, only to have a seg fault occur. – PaulMcKenzie Jun 07 '19 at 21:36
  • 2
    Definitely read the link for the Rule of Three. You cannot write robust, non-trivial C++ code without a good handle on the Rule of Three. Writing the assignment operator based on copy constructor rather than the other way around eliminates some code (self assignment becomes impossible), resource release is automated by the destruction of the copy, and move assignment comes free-of -charge. – user4581301 Jun 07 '19 at 21:38
  • Alright, looks like i need to do a lot of reading on C++ in general. Thank you for all your suggestions, i'll start with the Rule of Three! – Stelios Papamichail Jun 07 '19 at 21:41
2

Returning reference to the original object is needed for support of nested operations. Consider

a = b = c
SomeWittyUsername
  • 17,203
  • 3
  • 34
  • 78
2

We return a reference from the assignment operator so we can do some cool tricks like @SomeWittyUsername shows.

The object we want to return a reference to is the one who the operator is being called on, or this. So--like you've heard--you'll want to return *this.

So your assignment operator will probably look like:

Test& Test::operator=(const Test& t) {
    age = t.getAge();
    id = t.getID();
    return *this;
}

You may note that this looks strikingly similar to your copy-constructor. In more complicated classes, the assignment operator will do all the work of the copy-constructor, but in addition it'll have to safely remove any values the class was already storing.

Since this is a pretty simple class, we have nothing we need to safely remove. We can just re-assign both of the members. So this will be almost exactly the same as the copy-constructor.

Which means that we can actually simplify your constructor to just use the operator!

Test::Test(const Test& t) {
    *this = t;
}

Again, while this works for your simple class, in production code with more complicated classes, we'll usually want to use initialization lists for our constructors (read here for more):

Test::Test(const Test& t) : age(t.getAge()), id(t.getId()) { }
scohe001
  • 13,879
  • 2
  • 28
  • 47
  • funny thing is i actually wrote the initialization list variation at first but wasn't sure if it was correct. So in more complicated classes i should use that variation as is? I mean without doing anything inside `{}` or should i add the `remove values that the class already has stored` part in there? – Stelios Papamichail Jun 07 '19 at 21:37
  • @SteliosPapamichail since this is the constructor, it'll get called once (and only once!) when our object is first created. So we haven't actually stored anything yet! Even in a more complicated class, the only job of the constructor is to actually build up the object (and maybe allocate stuff if it needs it). In your case, there's no additional work that needs doing outside of assigning those two values, so leaving the body empty is fine. I added a link to my answer about why initialization lists are preferred. – scohe001 Jun 07 '19 at 21:38
  • As discussed in the comments of R Sahu's answer, prefer to write the assignment operator to use the copy constructor rather than writing the copy constructor using the assignment operator. It won't matter a bit here, but when the classes get more complex and management of resources becomes important, the [Copy and Swap](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) approach better manages the disposal of the resources and provides guarantees of correct exception handling (assuming the copy constructor and destructor are implemented correctly). – user4581301 Jun 07 '19 at 22:23