Why primitive to class type conversion destroys object values?

Why in the below code when I set c1 = 10 destroys all other values of variables (a, b, c) of the object. The statement should call the constructor and as the constructor is defined it sets the values of a to 10, but when I try to access values of b and c; it is giving me garbage values.

#include<iostream>
using namespace std;
class abc{
    private:
    // properties
    int a,b, c;
    
    public:
    
    void setdata(int x,int y)
    {
        a = x;
        b = y;
    }
    void showdata(){
        cout << "a = " << a << " b = " << b << "\n";
    }
    
    // constructors
    abc(){}
    
    abc(int k)
    {
        a=k;
    }
};
int main()
{
    
abc c1; // object intialization
c1.setdata(6,7); // setting values of properties
c1.showdata(); // printing values

c1 = 10; // primitive to class type conversion, constructor is being called
c1.showdata(); // why value of b and other variables is getting changed ?

return 0;

}

Solution 1:

This is equivalent to

c1 = abc(10);

The constructor abc(int k) doesn't initialize values b and c, therefore the member variables contain some random values. These random values, from the temporary abc(10) object, are then copied to c1.


The same is true for constructor abc(), here neither member variable is initialized, so they all contain "garbage" values. You will see this, when you call showdata right after construction

abc c1;
c1.showdata();

Solution 2:

In fact c1 = 10; invokes 2 distinct steps:

  • a temporary abc object is constructed from the abc(int k) constructor - obviously its b and c values are not set
  • that temporary is then assigned to c1 using one of the move assignement operators which will replace all the members.

After that, the temporary is destroyed.

If you have good reasons to not overide previous values in a copy, you could provide a custom copy or move assignment operator. But beware, that will be used for every assignment.

Solution 3:

When you wrote:

c1 = 10;

On the right hand side, the converting constructor is used to create a temporary object of type abc.

Next, the assignment operator is used to assign that temporary object on the right hand side to the object on the left hand side.

Also, note that the converting constructor abc(int k); only assign to the data member a and leave the data members b and c as it is. But since you've not explicitly initialized member b and c they have indeterminate value. And using/accessing those values(b and c) leads to undefined behavior.

This is why it is advised that

always initialize built in types in local/block scope.

To solve this you can use in-class initializers as shown below:

class abc{
    private:
    // USE IN-CLASS INITIALIZERS
    int a = 0,b = 0, c =0;
    
    public:
    
    void setdata(int x,int y)
    {
        a = x;
        b = y;
    }
    void showdata(){
        cout << "a = " << a << " b = " << b << "\n";
    }
    
    // constructors
    abc(){}
    
    abc(int k)
    {
        a=k;
    }
};

Also not that instead of assigning a value to the data member a inside the constructor body, you should use constructor initializer list to initialize it as shown below:

class abc{
    private:
    // USE IN-CLASS INITIALIZERS
    int a = 0,b = 0, c =0;
    
    public:
    
    void setdata(int x,int y)
    {
        a = x;
        b = y;
    }
    void showdata(){
        cout << "a = " << a << " b = " << b << "\n";
    }
    
    // constructors
    abc(){}
    //use constructor initializer list
    abc(int k): a{k}
    {
       //no need to assign to `a` here
    }
};