liveBook · Manning

Copy constructor

As explained in 100 C++ Mistakes and How to Avoid Them

A copy constructor is a C++ special member function that initializes an object from another object of the same class by copying its state.

Mistake: treating the copy constructor as interchangeable with operator=. A copy constructor initializes a new object from an existing one; operator= overwrites an already initialized object. The distinction matters whenever copying must preserve a class invariant. section 8.5 section 8.0

For ordinary value members such as integers, strings, and vectors, compiler-generated member-wise copying may be correct. Pointer-based memory and limited resources such as files, sockets, handles, or connections require an intentional policy instead. section 7.6 section 8.5

Check these consequences before accepting or writing a copy constructor:

  • A range-based loop variable declared by value copies each element; for class elements, that copy invokes the copy constructor. section 2.6
  • Returning a local object by value may call the copy constructor, while returning its pointer or reference creates undefined behavior. section 11.4

A shallow copy that fails

An InputReader containing a dynamically allocated filename array receives compiler-generated copying. InputReader ir2(ir1) and InputReader ir3 = ir2 copy the pointer rather than the array, so objects interfere and destruction can produce a segmentation fault. A correct constructor allocates a new array, copies each filename, and copies count.

Modernizing resource ownership

A Page with a raw body pointer can preserve uniqueness by constructing body with a copy of the source object, then deleting it in the destructor. Replacing that pointer with std::unique_ptr allows defaulted move construction and move assignment while retaining an explicit deep-copy constructor.

Use =default when compiler behavior expresses the intended semantics and =delete when an operation must be unavailable. If a class manages resources manually, treat the copy constructor, copy assignment operator, and destructor as one design problem; add move operations when ownership supports them. section 2.4 section 7.6

Listing 2.8 A simple class with defaulted and deleted members
class Container {
private:
    std::vector<int> values;
public:
    Container() = default;                                      #1
    Container(const Container& o) : values(o.values) {}
    Container& operator=(const Container& o) = delete;          #2
    void add(int n) { values.push_back(n); }
};
 
int main() {
    Container c1;
    c1.add(42);
    Container c2(c1);
    // c2 = c1;                                                 #3
    return 0;
}

Compare an explicit copy constructor with defaulted construction and deleted assignment. section 2.4

Listing 8.9 A properly written copy constructor and assignment operator
class InputReader {
private:
    std::string* filenames;
    int count;
public:
    InputReader() : filenames(new std::string[5]), count(0) {}
    ~InputReader() { delete[] filenames; }
    InputReader(const InputReader& r) : filenames(new std::string[5]), 
            count(r.count) {                                #1
        for (int i = 0; i < r.count; ++i)
            filenames[i] = r.filenames[i];
    }
    InputReader& operator=(const InputReader& r) {          #2
        for (int i = 0; i < r.count; ++i)
            filenames[i] = r.filenames[i];
        count = r.count;
        return *this;    }
    void addFileName(const std::string& n) {
        if (count == 5)
            throw std::runtime_error("too many files specified");
        filenames[count++] = n;
    }
    int getCount() const { return count; }
    const std::string& operator[](int index) {
        return filenames[index];
    }
};
 
int main() {
    InputReader ir1;
    ir1.addFileName("statistics.txt");
    InputReader ir2(ir1);                                 #3
    ir2.addFileName("numbers.txt");
    InputReader ir3 = ir2;                                #4
    ir3.addFileName("categories.txt");
    ir1.addFileName("questions.txt");
 
    for (int i = 0; i < ir1.getCount(); ++i)
        std::cout << ir1[i] << '\n';
    for (int i = 0; i < ir2.getCount(); ++i)
        std::cout << ir2[i] << '\n';
    for (int i = 0; i < ir3.getCount(); ++i)
        std::cout << ir3[i] << '\n';
    return 0;
}

Inspect a resource-owning copy constructor that allocates independent storage and copies its elements. section 8.5

How 100 C++ Mistakes and How to Avoid Them covers it

How deep it goes
Defines initialization semantics and demonstrates shallow versus deep copying, stopping short of exhaustive language rules.
Examples
Uses Container, Page, InputReader, range-based loops, and returning a locally constructed string to expose copying behavior.
What it assumes
Assumes basic C++ classes, constructors, pointers, dynamic allocation, and member functions while contrasting initialization with assignment.
What you can do after
Diagnose copying bugs, implement deep-copy behavior for resource-owning classes, and select defaulted, deleted, or move operations.
How it connects
Connects copy constructors with copy assignment, destructors, move operations, class invariants, range-based loops, and resource management.

Also explained in

Compare how 2 books explain copy constructor →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 C++ Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.