C++ Rule of Five: A class dealing with resources

Poby’s Home
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,

Errorneous example

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”

--

--

No responses yet