C/C++ Arena

Step 4 of 8

The copy problem

RAII classes have a hidden trap. When you copy an object, C++ by default copies each member. For a class holding a raw pointer, that copies the pointer, not the data it points to. Now two objects point at the same array:

a.data_ ──┐
          ├──> [ 1 2 3 ]
b.data_ ──┘

Changing b changes a. Worse, when both are destroyed, both destructors delete[] the same array: a double free, which crashes or corrupts memory.

The fix: a deep copy

Write a copy constructor, the constructor that builds a new object from an existing one of the same type. It takes a const reference to the original and allocates new memory with the same contents:

#include <iostream>
#include <cstring>

class Name {
public:
    explicit Name(const char* s) : text_(new char[std::strlen(s) + 1]) { std::strcpy(text_, s); }
    Name(const Name& other) : text_(new char[std::strlen(other.text_) + 1]) {
        std::strcpy(text_, other.text_);
    }
    ~Name() { delete[] text_; }
    Name& operator=(const Name&) = delete;
    void upper_first() { text_[0] = text_[0] - 'a' + 'A'; }
    const char* c_str() const { return text_; }

private:
    char* text_;
};

int main() {
    Name a("ada");
    Name b = a;
    b.upper_first();
    std::cout << a.c_str() << " " << b.c_str() << "\n";
}
ada Ada

Name b = a; calls the copy constructor, which gives b its own characters. Changing b doesn't affect a, and each destructor frees its own memory.

The rule of three

If a class needs a custom destructor, it almost certainly needs a custom copy constructor and copy assignment operator too, because all three deal with the same owned resource. (Here copy assignment is simply forbidden with = delete; the next steps cover both options.)

Your turn: add the deep-copy constructor. (Copy assignment is provided.)

Previous: RAII Next: Non-copyable classes