instance
instance

Reputation: 1374

Constructor behavior in case of default, parameterized, copy ctor and assignment operator

I was going through Thinking in C++ and have some confusion regarding the behaviors of constructors in C++. Here is my sample code:

#include<iostream>
using namespace std;

class base
{
    public:
           int a;

/*Ctor 1*/           base()  { cout<<"  default"<<endl; }

/*Ctor 2*/           base(int a){ cout<<"  base::int "<<endl; }

/*Ctor 3*/           base(const base& b) { cout<<"  base::cc "<<endl; }

/*Asgn 1*/           base operator=(base b){ 
                     cout<<"  base::assignment - base"<<endl;
                     return b;
                 }

/*Asgn 2*/     /*      base operator=(int b){
                     cout<<"  base::assignment - int"<<endl;
                     return (base)b;
                 } */
};
int main()
{
    base b;
    int a = 10;
    b = a;
    system("PAUSE");
    return 0;
}

Output :

program output

Could anyone please explain me the output ? I expected just a call to

  1. Default constructor.
  2. Parameterized constructor.

I am unable to understand why I get a call to assignment operator and copy constructor other object being "int" type. If I uncomment "Asgn 2' I get a call to it rather that Asgn 1 which is understandable.

If I am getting a call to copy constructor (which always take object reference as its parameter), is it because compiler casts int to base type?

Upvotes: 0

Views: 444

Answers (2)

Ed Heal
Ed Heal

Reputation: 59997

The output

default
base::int 
base::assignment - base
base::cc 

Comes about as follows:

base b;

Here create a b - This will use the default constructor

int a = 10;
b = a;

We have an assignment - the only one available takes a value of type base - so the compiler scratches its head and say "ah-ha" got a version of a constructor that can create an object of type base from an int. We can use that.

So you get the output

cout<<"  base::int "<<endl;

Now the compiler can use the assignment operator. The parameter is an object of type base but as it is temporary this does not need to be called (see http://en.cppreference.com/w/cpp/language/copy_elision), The assignment operator then outputs

cout<<"  base::assignment - base"<<endl;

But the assignment returns the value not as a reference - so it need to copy this return value into b - thus calling the copy constructor. Hence

cout<<"  base::cc "<<endl;

Upvotes: 2

curiousguy
curiousguy

Reputation: 8270

First of all, base(int a) is a converting constructor as

  • it takes one argument

  • it doesn't use the explicit keyword

A converting constructor can be used for implicit conversions:

void foo(base b);

void bar() {
    foo(3);
}

Here the int argument will be implicitly converted to base type with the converting constructor.

Because by value arguments (arguments passed by reference) are copied, the copy constructor is officially called; but here, the source of the copy is a temporary object, the implicitly created object using the int constructor. So the compiler is allowed to fuse the temporary and the parameter, directly constructing the target object. This optimisation is optional, and the compiler must still verify that the copy constructor could be called: that it is declared and accessible here (public).

Because the optimisation is very simple, almost all (or all?) compilers do it; many compilers do it at even the less aggressive optimisation levels (where most optimisations are disabled).

You declare the assignment operator as taking by a parameter by value and returning a copy (not a reference), which is quite rare (but not illegal):

/*Asgn 1*/           base operator=(base b){ 
                     cout<<"  base::assignment - base"<<endl;
                     return b;
                 }

This means that the copy constructor is needed to pass an argument to the assignment operator, and also for return instruction.

Please note that the fact it is an operator is irrelevant, you could have called it assign:

/*Asgn 1*/           base assign(base b){ 
                     cout<<"  base::assignment - base"<<endl;
                     return b;
                 }

and call it normally:

base a,b;
a.assign(b);
b.assign(base());
b.assign(base(2));
b.assign(3);

a.assign(b) will call the copy constructor to create the parameter of assign.

base() creates a temporary object using the default constructor, and base(2) creates one using the int constructor (when you explicitly create a temporary, it does not matter whether the constructor is a converting constructor). Then you can assign on the created temporary. The copy construction is avoided by the compiler by constructing directly the parameter.

In b.assign(3), the creation of the temporary is implicit, and the fact the constructor is a converting constructor is relevant.

The return statement creates another copy; the usual idiom for operator= is:

type& type::operator= (const type &source) {
    copy stuff
    return *this;
}

A reference is bound to the target object, and no redundant copying occurs.

Upvotes: 1

Related Questions