Operator Overloading & Resource Management

Copy Semantics: The Copy Constructor & Copy Assignment Operator

Why this matters

Run the worked example. You’ll see two correct-looking lines print – and then the program crashes, with a message about a “double free.” Nothing in main looks obviously wrong; the bug is in what Buffer b = a; does by default, and it’s one of the most common real mistakes in resource-owning C++ classes. This lesson is about exactly that line.

Buffer b = a; calls a third kind of constructor

Module 4 covered default and parameterized constructors. Buffer b = a; calls neither – it calls the copy constructor, invoked whenever a new object is created directly from an existing one of the same type. Just like the default constructor, if you never write one, C++ generates one for you automatically: a member-wise copy, copying each member’s value into the new object, one at a time, exactly as stored.

For a class like Module 4’s Point2D (two plain doubles), member-wise copy is exactly correct – copying x and y’s values independently is all “copying a point” could sensibly mean. For Buffer, it’s the bug: data is a pointer, and member-wise copy copies the pointer’s value – the address it holds – not the array it points at. After Buffer b = a;, a.data and b.data hold the same address. They aren’t two independent buffers; they’re two objects sharing one underlying array, each unaware the other exists.

Tracing the crash

b.data[0] = 99; then changing a.data[0] to 99 too (even though nothing touched a directly) is the first visible symptom – both prints happen correctly, so this part doesn’t even look like a crash yet, just a surprising result if you didn’t expect it. The real damage happens at the very end: when main returns, b is destroyed first (reverse order, from Module 4), running ~Buffer(), which calls delete[] data. Then a is destroyed, running ~Buffer() again – on the same address, already freed. This is a double free, and it’s what actually crashes the program. This particular copy – duplicating a pointer’s address without duplicating what it points to – is called a shallow copy, and it’s exactly what the compiler-generated copy constructor does for any class holding a raw resource.

Writing a correct copy constructor: a deep copy

Buffer(const Buffer &other) : data(new int[other.size]), size(other.size) {
    for (int i = 0; i < size; i++) {
        data[i] = other.data[i];
    }
}

A deep copy allocates its own fresh memory and copies the pointed-to contents into it, rather than copying the address. After this constructor runs, b.data and a.data point at two completely separate arrays with equal contents – modifying one has no effect on the other, and each object’s destructor frees its own memory, once, correctly.

The copy assignment operator: a different function, for a different situation

Buffer b = a; (creating b for the first time) calls the copy constructor. b = a;, where b already exists as a fully-constructed object, calls a different function entirely: the copy assignment operator, operator=. Mixing these up is a common early confusion – the syntax looks almost identical, but one is constructing a brand-new object, the other is overwriting an object that’s already alive and already owns its own resource, which needs to be released first.

Buffer &operator=(const Buffer &other) {
    if (this == &other) {
        return *this;
    }
    delete[] data;
    data = new int[other.size];
    size = other.size;
    for (int i = 0; i < size; i++) {
        data[i] = other.data[i];
    }
    return *this;
}

Three things this pattern always needs, in order: check for self-assignment first (if (this == &other)) – without it, a = a; would delete[] data before copying from other, except other is a, so you’d be copying from memory you just freed. Then release the old resource (delete[] data;) before acquiring a new one, or the old array leaks. Finally, allocate and copy, exactly like the copy constructor. It returns Buffer & – a reference to *this – for the same chaining reason operator+= did in the last lesson.

Try it
Output will appear here.

Exercises