1

I'm reading Prata's C++ book, and when talking about copy constructors, it says that this constructor is invoked when:

  1. Initializing a class object to the a class object of the same type.
  2. Passing an object by value to a function.
  3. Returning an object by value from a function.

Let say that we are working with a Vector class.

For understanding sakes, throughout the examples in the current chapter we included in the constructors/destructor definitions string outputs, to indicate which and when each of them is called.

For instance, if we have

int main()
{

    Vector v1;          // print "Default constructor called"
    Vector v2(9, 5);    // print "Constructor called"
    Vector v3 = v1;     // print "Copy Constructor called"

    return 0;
}

And Destructor called would be printed in this case 3 times, when exiting main().


In order to check the 3 points above I've been playing with a dumb_display() function, changing the type of the formal parameter/return value. In doing so, I got confused about what is indeed happening under the hood.

Vector dumb_display(Vector v)
{
    Vector test(45, 67);
    cout << "In dumb_display() function" << endl;
    cout << v << endl;
    return v;
}

Here:

  • Every time we return by value the passed argument as in the above function (argument either passed by value or reference) the copy constructor gets called.

    • It makes sense. It satisfies point 3.

Every time we return an object defined in the function's body (e.g., changing return v; by return test;), the copy constructor isn't called.

  • Point 3 isn't satisfied.

I'm having a hard time trying to understand this behaviour.

I don't know whether this is correct, but I think (since an automatic storage duration object is created once for the duration of the function call) once test is created it, hasn't have to be created again, because the object "is already there". This brings the question:

Why does returning the passed argument call the copy constructor twice? Why does the same object have to be created twice for the duration of the call to the function?

Biffen
  • 6,249
  • 6
  • 28
  • 36
asd
  • 1,017
  • 3
  • 15
  • 21
  • 6
    Go check out 'copy elision' and 'return value optimization' it's allowed to change the behavior of your program with regards to calling copy constructors. Your book is wrong/out of date. – xaxxon Mar 27 '18 at 08:35
  • 2
    Just so you know, the Prata *C++ Primer Plus* book [might not very good](https://accu.org/index.php?module=bookreviews&func=search&rid=17449). I recommend [this list of "good" books](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list/388282#3882829). – Some programmer dude Mar 27 '18 at 08:36
  • You should check out "Named Return Value Optimization" as it applies to your case. It is up to the compiler to decide if copy constructor would be elided. Anyway, make sure your code does not depend on *side-effects* of your copy constructor. See [this](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization) question. – Anirban Sarkar Mar 27 '18 at 11:22

2 Answers2

0
#include <vector>
#include <type_traits>
#include <tuple>
#include <iostream>

using namespace std;

struct S {
    S(){
        cout << "default constructor" << endl;   
    }
    S(S const &) {
        cout << "copy constructor" << endl;
    }
    S(S &&) {
        cout << "move constructor" << endl;
    }
    S & operator=(S const &) {
        cout << "copy assignment" << endl;
        return *this;
    }
    S & operator=(S &&) {
        cout << "move assignment" << endl;
        return *this;
    }
};

S f() {
    S s2;
    cout << "In f()" << endl;
    return s2;
}

S f2(S s) {
   cout << "In f2()" << endl;
   return s;
}

int main() {
    cout << "about to call f" << endl;
    S s2 = f();
    (void)s2;

    cout << endl << "about to call f2" << endl;
    S s3 = f2(s2);
    (void)s3;
}

results in:

about to call f
default constructor
In f()

about to call f2
copy constructor
In f2()
move constructor

In f(), the object is default constructed and return value optimization is used to actually construct it in place where it will actually end up -- in the s2 variable in main. No copy/move constructors are called.

In f2(), a copy is made for the input parameter for the function. That value is then moved into the variable s3 in main, again with return return value optimization.

live: https://wandbox.org/permlink/kvBHBJytaIuPj0YN

If you turn off return value optimization, you will see the results that you would expect from what your book says:

live: https://wandbox.org/permlink/BaysuTYJjlJmMGf6

Here's the same two examples without move operators if that's confusing you:

live: https://wandbox.org/permlink/c0brlus92psJtTCf

and without return value optimization:

live: https://wandbox.org/permlink/XSMaBnKTz2aZwgOm

it's the same number of constructor calls, just using the (potentially slower) copy constructor instead of the move constructor.

xaxxon
  • 19,189
  • 5
  • 50
  • 80
-1

The copy constructor is called twice because it is first copied from the function into the temperary value (which is represented by the function call and is what the returned value is, then copied into the variable, requiring two copies. Since this is not very efficient, there is also a "move" constructor, which is only needed once.

me'
  • 494
  • 3
  • 14