Step 1 of 6 · Reading · ~4 min
Learn
Modern C++ Patterns
The Rule of Five and RAII: Copying an Owner
You will write a class that owns a heap array so it can be destroyed, copied and assigned safely, and know which members the compiler still writes when you don't.
Until now the owners (vector, unique_ptr) were written by someone else; here you write one. The copy constructor takes another object of the same class.
The compiler's copy copies the pointer
Grid owns a rows-by-cols table of doubles in one block. new double[n]() allocates n doubles; the empty () zero-fills them. Its destructor is complete as written: free the array member.
class Grid {
public:
Grid(std::size_t r, std::size_t c)
: cells_(new double[r * c]()), rows_(r), cols_(c) {}
~Grid() { delete[] cells_; }
void set(std::size_t r, std::size_t c, double v) { cells_[r * cols_ + c] = v; }
double get(std::size_t r, std::size_t c) const { return cells_[r * cols_ + c]; }
private:
double* cells_;
std::size_t rows_, cols_;
};
Grid a(1, 2);
a.set(0, 0, 1.5);
Grid b = a; // compiler-written copy
b.set(0, 0, 9.0);
std::cout << "a(0,0) = " << a.get(0, 0) << std::endl;
a(0,0) = 9
Then a double-free abort prints on stderr (wording varies). The compiler's copy copies each member in order, so cells_ copies as an address: a and b share one block, and both destructors ran delete[] on it.
The copy constructor and copy assignment
A copy constructor takes const Grid& (a by-value parameter would need a copy itself) and builds its own block:
Grid(const Grid& src)
: cells_(new double[src.rows_ * src.cols_]), rows_(src.rows_), cols_(src.cols_) {
std::copy(src.cells_, src.cells_ + rows_ * cols_, cells_);
}
std::copy(first, last, dest) copies the range [first, last) into the elements starting at dest; pointers serve as iterators (the half-open range from the pointers lesson).
Copy assignment, operator=, overwrites an object that already exists (c = a;). Inside a member function, this points at the object it runs on and &src is the argument's address. In order:
- If
this == &src, return. Otherwisea = a;frees the block it is about to copy from, which is undefined behaviour: it may read back as zeros, old values, or crash. delete[]the blockcowns now, or it leaks.- Allocate a block for the source's size, which can differ.
- Update every size member (here
rows_andcols_). std::copythe elements.return *this;sox = y = zchains.
With c a 2x3 grid and a a 1x2 grid holding 1.5 2.5, c = a; then c.set(0, 0, 9.0) leaves a as 1.5 2.5 and c as 9 2.5. Freeing before allocating has a cost: if new fails, c has already lost its old block. Fixing that (copy-and-swap) is for later.
What the compiler still writes
The destructor, copy constructor, copy assignment and the move pair (next lesson) are the five special members; the Rule of Five says a class needing one for a raw resource almost always needs all five. Declaring one changes what the compiler writes for the others. A "still written" copy is the member-wise one (each member in order), so with a raw pointer it is the shallow copy again:
| You declare | Other copy operation | Move pair |
|---|---|---|
| destructor | still written | not written |
| copy constructor | copy assignment still written | not written |
| copy assignment | copy constructor still written | not written |
With no moves, every temporary is copied, allocating an array each time; next lesson fixes that.
Rule of Zero, = default, = delete
The cheapest fix is to write none of it: a std::vector<double> cells_ member makes the compiler's copy correct, because the vector copies its own elements. That is the Rule of Zero; write the five only when you hold a raw resource yourself.
= default asks for the compiler's version of a member; = delete removes it, so using it will not compile. Use it for locks and file handles, which must not be duplicated (delete operator= too).
Common mistakes
- Only the destructor.
Grid b = a;shares one block: changingbchangesa, then a double free aborts. - Assignment that skips
delete[]. Values print correctly, but each assignment leaks a block, so the live count ends above 0. - No
this == &srcguard.a = a;frees the block, then copies from it: garbage or a crash. - Forgetting a size member. After assigning a longer buffer to a shorter one the old length remains, so too few values print.
Your exercise
Finish IntBuffer with a destructor, copy constructor and copy assignment. The starter replaces the global operator new[] and operator delete[] with versions that only count blocks, and prints live= from that count, so a leak shows as a number above 0; you never touch it. Copy the elements, not the pointer; the right-hand side can have a different size.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…