Why should the copy constructor accept its parameter by reference in C++?

1 points by shivajikobardan ↗ HN

    #include <iostream>
    #include <stdlib.h>
    using namespace std;
    class Complex
    {
    private:
        int real;
        int img;
    
    public:
        Complex()
        {
            real = img = 0;
        }
    
        Complex(int r, int i)
        {
            real = r;
            img = i;
        }
    
        Complex(Complex &c)
        {
            real = c.real;
            img = c.img;
        }
    
        void putComplex()
        {
            cout << real << "+i" << img<<endl;
        }
    };
    int main()
    {
        system("cls");
        Complex c1;
        Complex c2(3, 5);
        Complex c3(c2);
        c1.putComplex();
        c2.putComplex();
        c3.putComplex();
    
        return 0;
    }
    
I don't understand how copy constructor works. 1) What happens when complex c3(c2) is called? Step by step? obviously, in short real gets c2.real and img gets c2.img, I'm asking for in depth explanation of behind the scenes.

2) Step by step what will happen if I remove the "&" from "c" in copy constructor definition? I've heard that it goes to infinite recursion, but I don't understand how.

I'll be happy with any references to read or answers. https://stackoverflow.com/questions/2685854/why-should-the-copy-constructor-accept-its-parameter-by-reference-in-c I've already read this answer and failed to understand from there.

7 comments

[ 2.6 ms ] story [ 22.5 ms ] thread
the most important point to keep in mind is the default pass by value context.

without defining a copy constructor, the compiler will do the copy himself, the point is : there is no copy constructor to call, so you have :

1) first the new Complex c3 is allocated,

2) there is no copy constructor to call

3) the compiler do the job, the memory used by c2 is copied on c3.

end of the story, no parameter value.

when adding a legal copy constructor (pass by reference), you may consider the reference like some sort of pointer. this is false, but help to understand the memory behavior of a copy constructor. then you have something like :

1) first the new Complex c3 is allocated

2) the "memory" for c, the reference/pointer to c2, is allocated.

3) the _address_ of c2 is copied in c.

4) the c3 constructor start with the reference/pointer/address of c2 as parameter.

end of the story, normal "pass by value" of an address.

finaly, if a copy constructor _without_ reference/pointer was legal, you will have:

1) first the new Complex c3 is allocated,

2) another new Complex, the c3 copy constructor parameter value c, is allocated.

3) instead of directly copying c2 memory to c, there is the c copy constructor to call (with a copy of c2 as parameter value)

4) another new Complex c_bis, the c copy constructor parameter value (c), is allocated.

5) instead of directly copying c2 memory to c_bis there is the c_bis copy constructor to call (with a copy of c2 as parameter value)

6) another new Complex c_bis_bis is allocated ... etc.

the problem is that a copy constructor is _implicitly_ called to create a copy, which transform a pass by value semantic in an infinite loop of allocations on the stack.

hope this help.

Correct me if I'm wrong: In int main, "complex c3(c2)" creates a copy of c2 and thus passes c2 to copy constructor.

In copy constructor, "complex(complex c)" copy constructor takes c=c2.

So what's the issue with this?

> Correct me if I'm wrong: In int main, "complex c3(c2)" creates a copy of c2 and thus passes c2 to copy constructor.

it never pass c2 to the copy constructor.

a) it pass a reference to it, if the copy constructor is defined as "Complex(Complex& refr)"

b) it pass a copy of it, if the copy constructor is defined as "Complex(Complex copy).

just dont consider references in the context of objects or copy constructors, but in the context of a simple function :

int incr1(int value) { value = value + 1; return value; }

vs

int incr2(int& value) { value = value + 1; return value; }

in incr1 value is local to the function, it is a copy of the value you pass as parameter

in incr2 it is'nt local to the function, there is no copy done.

// not tested sample :

#include <assert.h>

void main() {

  int a_value = 2;

  int result1 = incr1(a_value); // no reference, a copy, local to the function, is made.
  assert(a_value == 2);         // ok
  assert(result1 == 3);         // ok

  int result2 = incr2(a_value); // with reference, there is no copy made.
  assert(a_value == 2);         // error, the original a_value parameter as been incremented.
  assert(a_value == 3);         // ok
  assert(result2 == 3);         // ok
}

the incr1 is more "secure" (it is said "pure", for this and others reasons), but it always make a temporary int that is a copy of the parameter you give to it.

the incr2 is less "secure", but there is no temporary int created.

that's exactly to prevent the temporary int/Complex that copy constructors always require a reference, because when you call the copy constructor explicitly (in : Complex c3(c2); ), the copy constructor for the temporary will be called implicitly too, and the copy constructor for the temporary of the temporary called implicitly will be called implicitly too, etc.

hope this help.

i've seen you'v been recommended to learn C in another thread. in C++, the use of "references" really require to understand the concept of pointers, they'r basically the same things but not exchangeable, because references hide some aspects of pointers that make the syntax a little more shitty. in fact the syntax of references (the use of '&' in parameters) come from the syntax of pointers, it means "address of".

int incr2(int& value) { value = value + 1; return value; }

is the same as

int incr3(int* value) { (*value) = (*value) + 1; return *value; }

and they will be used diferently for the same result :

void main() {

  int a_value = 5;

  int result2 = incr2(a_value); // result2 = a_value = 6;
  int result3 = incr3(&a_value); // resulst3 = a_value = 7;
}

in the call to incr3 "&a_value" can be read : address_of "a_value".

and in the body of incr3 "*value" can be read : content of address "value".

and finnaly in the prototype "int incr3(int* value)", "int*" can be read : pointer to an int, or address of an int.

pointer/address are a fundamental concept for programming at large, the concept of "indirection".

all this may seem unrelated at first, but in your Complex problem, you can try something like :

#include <iostream>

...

Complex c2;

std::cout << &c2 /* address of c2 */ << std::endl;

and in your copy constructor :

Complex(Complex& c) {

  std::cout << &c /\* address of c \*/ << std::endl;

  ...
}

you will see that, with a reference, the addresses are the sames, but without references you will get different addresses meaning that your variables are'nt the same (in the case of function parameters/arguments they are copies of the parameters values, that's why we call this "pass by value" semantic, rather than "pass by reference" or "pass by address" or box, or indirection, or whatever having the same meaning).

hope this help.

(comment deleted)