C & C++ DISCUSSION

C++ class owning a raw buffer crashes after being copied: what is the Rule of Three/Five?

Started by as rule of threecopy constructormove semanticsdouble freeRAII
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

C++ class owning a raw buffer crashes after being copied: what is the Rule of Three/Five?

as C & C++ Forum
#1

I wrote a small Buffer class: the constructor does data = new uint8_t[size]; and the destructor does delete[] data;. It works until I pass a Buffer to a function by value or store one in a std::vector. Then the program crashes on exit with a double free, or the contents of one buffer change when I write to another.

I never wrote any copying code, so what is being copied, and what do I need to add to make the class safe?

Community replies 5

Re: C++ class owning a raw buffer crashes after being copied: what is the Rule of Three/Five?

#2

If you declare no copy operations, the compiler generates a copy constructor and a copy assignment operator that copy each member. For a pointer member that means copying the address, not the array. After Buffer b = a; both objects hold the same pointer: writes through one show up in the other, and when both destructors run, delete[] is called twice on the same block. That is undefined behaviour and usually a crash. Passing by value and std::vector growth both make such copies.

The Rule of Three: a class that needs a user-written destructor, copy constructor or copy assignment operator almost always needs all three.

Re: C++ class owning a raw buffer crashes after being copied: what is the Rule of Three/Five?

#3

A deep copy constructor allocates its own block: Buffer(const Buffer& o) : size(o.size), data(new uint8_t[o.size]) { std::copy(o.data, o.data + o.size, data); }.

Assignment must also release the old block and survive self-assignment such as a = a;. The tidy way is copy-and-swap: take the parameter by value and swap with it, Buffer& operator=(Buffer o) { std::swap(size, o.size); std::swap(data, o.data); return *this; }. The copy is made by the copy constructor, the old block is freed when o is destroyed at the end of the function, and if the allocation throws, *this has not been touched.

Re: C++ class owning a raw buffer crashes after being copied: what is the Rule of Three/Five?

#4

Since C++11 the rule is five: add a move constructor and move assignment, which take over the pointer instead of copying the array: Buffer(Buffer&& o) noexcept : size(o.size), data(o.data) { o.data = nullptr; o.size = 0; }. delete[] on a null pointer does nothing, so the moved-from object is destroyed safely.

Mark it noexcept. std::vector only moves its elements during reallocation when the move constructor cannot throw; otherwise it falls back to copying. Note also that declaring a destructor or copy operations suppresses the implicit move operations, so without this constructor every "move" of your class is silently a deep copy.

Re: C++ class owning a raw buffer crashes after being copied: what is the Rule of Three/Five?

#5

Better still is the Rule of Zero: do not own raw memory at all. Replace the pointer and size with std::vector<uint8_t> data; and delete your destructor. The compiler-generated copy, move and destructor then do the right thing, because each member knows how to manage itself.

If the class should be movable but not copyable, hold a std::unique_ptr<uint8_t[]> instead. The class becomes move-only automatically, and an accidental copy is a compile error rather than a crash at run time.

Re: C++ class owning a raw buffer crashes after being copied: what is the Rule of Three/Five?

#6

For classes that wrap a hardware resource or a handle, copying usually makes no sense, so say so: Buffer(const Buffer&) = delete; Buffer& operator=(const Buffer&) = delete;. Passing by value then fails to compile and the error points at the offending line.

To confirm a diagnosis like yours, build the code on a PC with -fsanitize=address; AddressSanitizer reports the double free together with the call stacks of the allocation and of both frees. And pass large objects as const Buffer& when a function only reads them. That avoids the copy altogether, which matters on a microcontroller with a few kilobytes of heap.

TEP COMMUNITY