3

I wrote the following c++ code trying to understand copy elision in c++.

#include <iostream>
using namespace std;

class B
{
  public:  
  B(int x  ) //default constructor
  {
    cout << "Constructor called" << endl;
  }    

  B(const B &b)  //copy constructor
  {
     cout << "Copy constructor called" << endl;
  } 
};

int main()
{ 

  B ob =5; 
  ob=6;
  ob=7;

  return 0;
}

This produces the following output:

Constructor called
Constructor called
Constructor called

I fail to understand why is the constructor being called thrice with each assignment to object ob.

Abhay Sharma
  • 309
  • 5
  • 16
  • The thing about constructor elision is that it's an optimization permitted by the language, and not a requirement of the language. This means it isn't always done (depending on your optimization level, etc.). In particular, usually the compiler won't elide constructor calls if they have side effects (like yours does here). – Cameron Jul 08 '15 at 18:39
  • Implicit conversions can use constructors. Here, since `int` is implicitly convertible to `B` with the constructor `B(int)`, that constructor is called in all three lines. The copy-constructor (`B(const B&)`) can also be called on the first line if it weren't for [copy-elision](http://en.cppreference.com/w/cpp/language/copy_elision). – David G Jul 08 '15 at 18:40
  • Your first constructor, by the way, is technically called a custom constructor (default constructors can be called with no arguments). – Cameron Jul 08 '15 at 18:42
  • Side note: `B(int x)` is no default constructor. That is `B()`. –  Jul 08 '15 at 18:43
  • 1
    Side note: You may experiment `explicit B(int x)` –  Jul 08 '15 at 18:46

3 Answers3

5

B ob =5;

This uses the given constructor.

ob=6;

This uses the given constructor because there is not a B& operator=(int) function and 6 must be converted to type B. One path to do this is to temporarily construct a B and use it in the assignment.

ob=7;

Same answer as above.

I fail to understand why is the constructor being called thrice with each assignment

As I stated above you do not have a B& operator=(int) function but the compiler is happy to provide a copy assignment operator (i.e., B& operator=(const B&);) for you automatically. The compiler generated assignment operator is being called and it takes a B type and all int types can be converted to a B type (via the constructor you provided).

Note: You can disable the implicit conversion by using explicit (i.e., explicit B(int x);) and I would recommend the use of explicit except when implicit conversions are desired.

Example

#include <iostream>

class B
{
public:
    B(int x) { std::cout << "B ctor\n"; }

    B(const B& b) { std::cout << B copy ctor\n"; }
};

B createB()
{
    B b = 5;
    return b;
}

int main()
{
    B b = createB();

    return 0;
}

Example Output

Note: Compiled using Visual Studio 2013 (Release)

B ctor

This shows the copy constructor was elided (i.e., the B instance in the createB function is triggered but no other constructors).

Community
  • 1
  • 1
James Adkison
  • 9,412
  • 2
  • 29
  • 43
  • Is there a special provision in cpp to create a temporary object and call a 1 parameter constructor (if given explicitly ) in such cases instead of an error saying primitive type int can't be assigned to class object? – Abhay Sharma Jul 08 '15 at 18:48
  • 2
    You can prevent implicit conversions by using `explicit` (e.g., `explicit B(int x);`) which causes `B b = 5` to produce a compilation error. I would recommend using `explicit` unless you are wanting implicit conversions. – James Adkison Jul 08 '15 at 18:57
4

Each time you assign an instance of the variable ob of type B an integer value, you are basically constructing a new instance of B thus calling the constructor. Think about it, how else would the compiler know how to create an instance of B if not through the constructor taking an int as parameter?

If you overloaded the assignment operator for your class B taking an int, it would be called:

B& operator=(int rhs)
{
    cout << "Assignment operator" << endl;
}

This would result in the first line: B ob = 5; to use the constructor, while the two following would use the assignment operator, see for yourself:

Constructor called
Assignment operator
Assignment operator

http://ideone.com/fAjoA4

If you do not want your constructor taking an int to be called upon assignment, you can declare it explicit like this:

explicit B(int x)
{
    cout << "Constructor called" << endl;
}

This would cause a compiler error with your code, since it would no longer be allowed to implicitly construct an instance of B from an integer, instead it would have to be done explicitly, like this:

B ob(5);

On a side note, your constructor taking an int as parameter, is not a default constructor, a default constructor is a constructor which can be called with no arguments.

Tommy Andersen
  • 7,165
  • 1
  • 31
  • 50
3

You are not taking the assignment operator into account. Since you have not defined your own operator=() implementation, the compiler generates a default operator=(const B&) implementation instead. Thus, your code is effectively doing the following logic:

#include <iostream>

using namespace std;

class B
{
  public:  
  B(int x) //custom constructor
  {
    cout << "Constructor called" << endl;
  }    

  B(const B &b)  //copy constructor
  {
     cout << "Copy constructor called" << endl;
  } 

  B& operator=(const B &b)  //default assignment operator
  {
     return *this;
  } 
};

int main()
{ 
  B ob(5); 
  ob.operator=(B(6));
  ob.operator=(B(7));

  return 0;
}

The compiler-generated operator=() operator expects a B object as input, but you are passing an int value instead. Since B has a non-explicit constructor that accepts an int as input, the compiler is free to perform an implicit conversion from int to B using a temporary object.

That is why you are seeing your constructor invoked three times - the two assignments are creating temporary B objects.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770