Today my question is why is a constructor called in absence of a user provided assignment operator? I know the difference between constructor and assignment operator in relation to the =
symbol. If an object did not exist previously before that line, the constructor is implicitly called.
std::string name = "Bjarne"; // implicit constructor
If an object already existed previously before that line, then assignment operator is called.
std::string name;
name = "Bjarne"; // assignment operator
Now I have a class Number
here, which is a tagged union. It is simply a number, means either an int
or a float
. I have multiple conversion constructors defined which make it easy for the user to assign values to the Number
implicitly. I expect them to be called in such situation:
Number num1 = 8;
In order to know for sure which constructor is called when, I put print statements in the bodies of the constructors.
#include <cstdlib>
#include <iostream>
using std::cin;
using std::cout;
using std::cerr;
using std::endl;
using std::ostream;
/* Unions can be anonymous (unnamed).
* This form is used when nesting a union inside a struct or class that contains an extra data member
* to indicate what the union contains.
* This arrangement is called a tagged union (or managed union).
* It can be used as a "polymorphic" class which can be several things at once, for example to create an array
* of different data types.
*/
class Number {
friend ostream& operator<<(ostream& os, Number& num);
public:
Number(int i = 0) : DATA_TYPE(INTEGER), i(i) { cerr << "Number(" << i << ")\n"; }
Number(unsigned int u) : DATA_TYPE(INTEGER), i(static_cast<int>(u)) { cerr << "Number((signed) " << i << ")\n"; }
Number(float f) : DATA_TYPE(FLOAT), f(f) { cerr << "Number(" << f << ")\n"; }
Number(double d) : DATA_TYPE(FLOAT), f(static_cast<float>(d)) { cerr << "Number((float) " << f << ")\n"; }
Number(const Number& rhs) : DATA_TYPE(rhs.DATA_TYPE), i(rhs.i) { cerr << "Number((Number) " << *this << ")\n"; }
private:
enum data_type { INTEGER, FLOAT } DATA_TYPE;
union {
int i;
float f;
};
};
ostream& operator<<(ostream& os, Number& num)
{
switch (num.DATA_TYPE) {
case Number::INTEGER:
cout << num.i;
break;
case Number::FLOAT:
cout << num.f;
break;
default:
cout << 0;
break;
}
return os;
}
int main()
{
Number num1 = 5; // conversion constructor
cout << num1 << endl;
num1 = 12.5;
cout << num1 << endl;
return EXIT_SUCCESS;
}
When I run this code it produces unexpected output for me:
Number(5)
5
Number((float) 12.5)
12.5
I see that when Number num1
is initialized, the constructor taking an int
as a parameter is called. Then I print the value of num1
. Then on the next line it seems that the constructor taking a double
as a parameter is called. This is strange. Before that line, num1
already existed as an object. So it should logically call assignment operator instead. No assignment operator was defined by me instead, so I assume that the assignment operator generated by the compiler will be called.
I want to know, in the absence of a user provided assignment operator, what happens when I do this? num1 = something;
Does the default assignment operator provided by the compiler get called or does a constructor get called? What happens when the constructor is called on an object that already exists? Does that object get "re-constructed"? So what actually happens in that case?
I want to know, in the absence of a user provided assignment operator, what happens when I do this
num1 = something;
?
num1 = 12.5;
calls implicit Number::operator =(const Number&)
.
So constructs a temporary number from double
.
Does the default assignment operator provided by the compiler get called or does a constructor get called?
Both. Constructor is called for the temporary, not the existing object.
What happens when the constructor is called on an object that already exists? Does that object get "re-constructed"? So what actually happens in that case?
You cannot recall constructor on existing object. The nearest thing you can do is placement-new.