C++ Rule of Five: A class dealing with resources
3 min readMar 6, 2021
RAII — Resource Allocation Is Initialization
From Arthur O’Dwy’s cppCon presentation
My example
#include <iostream>
#include <string.h>
#include <utility>class Entity
{
public:
Entity() = delete;
Entity(size_t storageSize) : mSize(storageSize)
{
mStorage = new int[mSize];
} // 5 custom made building blocks if a class manages "resources"
// - destructor
// - copy constructor
// - copy assignment operator
// - move constructor
// - move assignment operator // destructor
virtual ~Entity()
{
if (mStorage != nullptr) {
delete[] mStorage;
mStorage = nullptr;
}
} // copy constructor
Entity(const Entity& rhs)
{
if (this != &rhs) {
mStorage = new int[rhs.mSize];
mSize = rhs.mSize;
memcpy(mStorage, rhs.mStorage, rhs.mSize*sizeof(int));
}
} // copy assignment operator
Entity& operator=(const Entity& rhs)
{
if (this != &rhs) {
delete[] mStorage;
mSize = rhs.mSize;
mStorage = new int[mSize];
memcpy(mStorage, rhs.mStorage, rhs.mSize*sizeof(int));
}
return *this;
} // move constructor
Entity(Entity&& rhs)
{
mSize = std::exchange(rhs.mSize, 0);
mStorage = std::exchange(rhs.mStorage, nullptr);
} // move assignment operator
Entity& operator=(Entity&& rhs)
{
mSize = std::exchange(rhs.mSize, 0);
mStorage = std::exchange(rhs.mStorage, nullptr);
return *this;
} void fillWith(int value)
{
for (size_t i=0; i<mSize; i++) {
mStorage[i] = value;
}
} void print()
{
std::cout << "size: "<< mSize << ", storage:" << mStorage << std::endl;
for (size_t i=0; i<mSize; i++) {
std::cout << mStorage[i] << std::endl;
}
}private:
int* mStorage{nullptr};
size_t mSize{1};
};int main()
{
std::cout << "// constructor" << std::endl;
Entity e1(2);
e1.fillWith(99);
std::cout << "e1 constructed:";
e1.print(); std::cout << "// copy constructor" << std::endl;
Entity e2 = e1;
std::cout << "e2 copy constructed from e1:";
e2.print(); std::cout << "// move constructor" << std::endl;
Entity e3(std::move(e1));
std::cout << "e3 move constructed from e1:";
e3.print();
std::cout << "e1 after moved:";
e1.print(); // now e1 is gone. don't use it after this.std::cout << "// copy assignment" << std::endl;
Entity e4(4);
e4 = e3;
std::cout << "e4 copy assigned from e3:";
e4.print();std::cout << "// move assignment" << std::endl;
Entity e5(3);
e5 = std::move(e4);
std::cout << "e5 move assigned from e4:";
e5.print();
std::cout << "e4 after move:"; e4.print();}
result
Assignment operator overloading - copy and swap idiom
above copy and swap idiom can be used for both
- copy assignment operator
- move assignment operator
because swap
- A swap function is a non-throwing function that swaps two objects of a class, member for member.
- If
rhs
is being initialized with an rvalue, it will be move-constructed. C++11 will automatically pick the move-constructor when appropriate as well. - In a move assignment operator, just call swap . The object being moved into will get the resources, and the other object’s destructor will free the original ones
So, don’t provide multiple similar ones. The compiler will be confused.
If you do below,
So, in conclusion, just provide one “copy and swap” assignment operator. This will work for both copy assignment and move assignment.
This is what Arthur calls as “The Rule of Four”