1
#include <iostream>

using namespace std;

struct Car
{
    int size = 4;
    int* tires = nullptr;

    Car()
    {
        cout << "ctor" << endl;
        tires = new int[size];
    }

    Car(const Car& car)
    {
        cout << "copy ctor" << endl;
        tires = new int[size];
        memcpy(tires, car.tires, size*sizeof(int));
    }

    Car& operator= (Car& car)
    {
        cout << "copy assignment operator" << endl;
        tires = new int[size];
        memcpy(tires, car.tires, size*sizeof(int));
        return *this;
    }

     ~Car()
    {
        cout << "dtor" << endl;
        delete tires;
    }
};

int main()
{
    cout  << "starting..." << endl;

    Car car1;
    car1.tires[0] = 2;

//    Car car2(car1); // #1
    Car car2 = car1; // #2 I was expecting the assingment operator to be called here

    cout << car1.tires[0] << " " << car2.tires[0] << endl;

    return 0;
}

I understand why the copy constructor is called at #1, but how does it end up being called at #2?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
KcFnMi
  • 5,516
  • 10
  • 62
  • 136
  • 4
    Assignment is when you assign to a variable after creating it: `Car car2; car2 = car1;` – HolyBlackCat Feb 03 '23 at 05:50
  • When `size` is always `4`, you can change `int* tires = nullptr;` to `int tires[4];`, then you do not have to `new` all that memory and you do not have to change `delete tires;` to `delete[] tires;`, because you can remove the line at all. – mch Feb 03 '23 at 08:18
  • 1
    FYI -- 1) Your `operator=` leaks memory. You failed to `delete[]` the old memory. 2) You used the wrong form of `delete` in the destructor. It should be `delete []`. – PaulMcKenzie Feb 03 '23 at 08:53

2 Answers2

5

The statement:

Car car2 = car1;

is NOT assignment, like you are thinking. Assignment can happen only on an existing object, not when an object is being created.

The statement above is actually initialization instead, specifically Copy Initialization:

Syntax

T object = other; (1)

...

The effects of copy initialization are:

  • ...

  • Otherwise, if T is a class type and the cv-unqualified version of the type of other is T or a class derived from T, the non-explicit constructors of T are examined and the best match is selected by overload resolution. That constructor is then called to initialize the object.

  • ...

So, in your case, the compiler finds the copy constructor is a match and treats the statement as-if you had written Car car2(car1); instead.

That is why the copy constructor is called.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    And making the copy constructor explicit, `explicit Car(const Car& car)`, seems to prevent that of happening. – KcFnMi Feb 03 '23 at 07:27
1

A C++ compiler will call the assignment operator only for an object that already exists, to overwrite the values of this object with the values of another instance.

car car1,car2;
car2=car1;

And it will call the copy constructor in the below cases:

  1. if the object is initialized with another instance.

      car car1;
      car car2=car1; //This is equivalent to car2(car1);
    
  2. if an object is passed to a function as a non-reference parameter.

    Here you can see the parameter of func, where the obj2 is being initialized and not assigned.

     void func(car obj2)//not a reference parameter(car& obj2)
     {
      //
     }
    
     car obj1;
     func(obj1);
    
  3. An object is returned from a function - I don't have an idea about this yet.

Initialization is when a new object or variable is being created to hold a value, and assignment is when an existing object or variable is set to hold a new value.

For the above assignment operator example, both the car1 and car2 objects hold a value (probably garbage) at the time of creation, and then are set to a new value.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Indu
  • 37
  • 8