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

