Search code examples
c++delete-operatorrule-of-three

Implementation of "Rule of Three" gone wrong


Below is an erroneous implementation of "The rule of three", which I try to understand.

Debugging the program, I found that the debugger has a problem cleaning up int *k, which could be resolved either by defining int *k = nullptr or simply setting it to something reasonable in the copy constructor.

However, I do not understand how the resulting error of the program, an access violation, comes into existence.

I do know, after the copy assignment constructor v1's int *kno longer points to a valid memory address.

class Vector2 {
public:
    std::string name = "default";
    int* k;

    Vector2(int s, std::string n) : k(new int[s]), name(n) {
    }

    Vector2(const Vector2 &other)  {
        std::cout<< "Copy constructor: " << name << std::endl;
    }

    ~Vector2() {
        std::cout << "Deleting: " << name << std::endl;
        delete[] k;
    }

    void swap(Vector2& other) {
        using std::swap;
        swap(k, other.k);
    }

    Vector2& operator=(Vector2 other) {
        std::cout << "Copy assignment constructor: " << name << std::endl;
        swap(other);
        return *this;
    }
};


int main() {
        Vector2 v1 = Vector2(2, "v1");
        Vector2 v2 = Vector2(4, "v2");
        v1 = v2;
        std::cout << &v1 << " " << &v2 << std::endl;
        std::cout << &v1.k << " " << &v2.k << std::endl;
        return 0;
    }

Below is the console output of above's program:

Copy constructor: default
Copy assignment constructor: v1
Deleting: default
0000001B5611FA28 0000001B5611FA78
0000001B5611FA50 0000001B5611FAA0
Deleting: v2
Deleting: v1
16:18:42: The program has unexpectedly finished.

Solution

  • The solution can be derived by laying out the exact sequence of events, e.g.: More print outs and testing, what parameters are called when:

    Starting in: v1 = v2;

    1. v2 calls copy constructor with the argument other (whatever other is), in particular: its int* k does not point to valid memory. For simplicity let's call this new Vector2 v3.
    2. The copy assignment constructor is called now with v3.
    3. Then we begin swap.

    The error actually arises within the copy constructor, as v3 is not initialised properly in step 1 .

    Step 2 and step 3 are basically "hiding", transferring the error from v3 onto v1.

    The interesting question now is, how is v3 actually generated? Not by the default constructor!