When is copy constructor called?

The question is pretty much in the title, I need help with understanding where exactly the copy constructor is called, as the output of this code doesn't match what I learned in class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  #include <iostream>
  using namespace std;

  class XX {
  public:
  	XX(int);
	XX(const XX&);
  private:
	int ID;
  };

  XX::XX(int IDD) {
	ID = IDD;
  }

  XX::XX(const XX& obj) : ID(obj.ID)
  {
	cout << "copy constructor" << endl;
  }

  XX f(XX x1) {
	cout << "f begin" << endl;
	XX x2 = x1;
	cout << "f end" << endl;
	return x2;
  }

  void g() {
	cout << "g begin"<<endl;
	XX xa = 3, xb = 1;
	xa = f(xb);
	cout << "g end" << endl;
  }

  int main() {
	g();

	return 0;
  }


output:
g begin
copy constructor
f begin
copy constructor
f end
copy constructor
g end
First copy constructor: Line 21
Second copy constructor: Line 23
Third copy constructor: Line 31
Are you sure that is not 31,23,25?
When I changed return x2, to return 2; I didn't get a third copy constructor called.
And this rise my question in class teacher said that the copy constructor would be called in both line 21 and line 25, did he was just wrong?
Try this (it has more descriptive messages for copy and move construction):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <string>

struct X
{
    explicit X( int id = 0 ) noexcept : id(id)
    { std::cout << "X::constructor: id == " << id << "  object at " << this << '\n' ; }

    X( const X& that ) noexcept : id(that.id)
    {
        std::cout << "X::copy_constructor: id == " << id << "  object at " << this
                  << ", copied from object at " << std::addressof(that) << '\n' ;
    }

    X( X&& that ) noexcept : id(that.id)
    {
        std::cout << "X::move_constructor: id == " << id << "  object at " << this
                  << ", moved from object at " << std::addressof(that) << '\n' ;
    }

    ~X()
    { std::cout << "X::destructor: id == " << id << "  object at " << this << '\n' ; }

   X& operator= ( const X& ) = default ;
   X& operator= ( X&& ) = default ;

    int id ;
    std::string text = "id: " + std::to_string(id) ;
};

X foo()
{
    return X(2) ; // mandatory copy elision
    // https://en.cppreference.com/w/cpp/language/copy_elision
}

X bar()
{
    X temp(3) ;
    temp.id = 3 ;
    return temp ; // permissible copy elision
    // https://en.cppreference.com/w/cpp/language/copy_elision
}

int main()
{
    X a(1) ; // X::constructor: id == 1  object at address of a

    const X b(a) ; // X::copy_constructor: id == 1  object at address of b, copied from object at address of a

    const X c = foo() ; // X::constructor: id == 2  object at address of c (mandatory copy elision)

    const X d = bar() ; // X::constructor: id == 3  object at address of d (typical: permissible copy elision)


    X e( std::move(a) ) ; // X::move_constructor: id == 1  object at address of e, moved from object at address of a
    e.id = 99 ; // to distinguish its id from a.id

    std::cout << "\n---------------\n\n" ;

    // destructors called in reverse order of construction
}

http://coliru.stacked-crooked.com/a/bc1aeb9a5be1a51f
What you get depends upon what version of C++ you're using! With VS2022 I only get 2 copy constructor:


g begin
copy constructor
f begin
copy constructor
f end
g end


which is:

L21
L23

L31 depending upon the C++ version does/doesn't invoke a copy constructor but does copy elision instead.
Last edited on
L32 (in the original code) is implementation dependant (not really version dependant post C++11);
copy elision in this situation is permitted, but not mandatory.

Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

. In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".
...

https://en.cppreference.com/w/cpp/language/copy_elision#Mandatory_elision_of_copy/move%20operations

In particular:
Because some compilers do not perform copy elision in every situation where it is allowed (e.g., in debug mode), programs that rely on the side-effects of copy/move constructors and destructors are not portable.

https://en.cppreference.com/w/cpp/language/copy_elision#Notes
Last edited on
Why the first copy on line 21? The [parameter] variable x1 is not accessible to the caller only to the callee.

Why the third copy on line 31? The returned [temporary] object is created on the caller stack and is not accessible to the callee.

The constructor of the object is supposed to be called where it is created.
Topic archived. No new replies allowed.