Dynamic Memory and RAII

Introduction to RAII

Important: This lesson uses classes in the code examples which we haven't covered yet, so you can SKIP it until you learn about classes, in the Classes and Objects category.

A manual release of a resource only occurs when all paths of the code remember to release the resource. Even one early return, lost error path, or simple omission is sufficient to leak memory or keep a resource in use. RAII removes this burden by binding the lifetime of a resource to the lifetime of a normal object.

The underlying idea is simple. One class acquires the resource in the constructor and releases it in the destructor. At the point of creation of an object of that class, the resource is acquired; when the object's lifetime comes to an end, the resource is released by the language itself. The programmer never needs to write the release manually.

A minimal example follows; the object owns a single heap allocated int.

#include <iostream>

class IntOwner
{
public:
    IntOwner(int value)
        : ptr{ new int{ value } }
    {
    }

    IntOwner(const IntOwner&) = delete;
    IntOwner& operator=(const IntOwner&) = delete;

    ~IntOwner()
    {
        delete ptr;
    }

    int get() const
    {
        return *ptr;
    }

private:
    int* ptr;
};

int main()
{
    IntOwner owner{ 42 };
    std::cout << owner.get() << '\n';

    return 0;
}

When the object owner is created, the IntOwner class's constructor is run. The member initializer ptr{ new int{ value } } then allocates a heap int and stores its pointer in owner's ptr member. When the function main finally reaches its terminating brace, owner is destroyed and IntOwner's destructor is run.

The destructor runs delete ptr, releasing the memory on the heap.

In main, the programmer never wrote a direct delete; the compiler invoked the destructor upon leaving scope, effectively doing what had to be done.

In the following case with an early return from the scope:

#include <iostream>

class IntOwner
{
public:
    IntOwner(int value)
        : ptr{ new int{ value } }
    {
    }

    IntOwner(const IntOwner&) = delete;
    IntOwner& operator=(const IntOwner&) = delete;

    ~IntOwner()
    {
        delete ptr;
    }

    int get() const
    {
        return *ptr;
    }

private:
    int* ptr;
};

int main()
{
    IntOwner owner{ 99 };
    std::cout << owner.get() << '\n';

    return 0; // Destructor still runs
}

The return statement immediately aborts the function main; however, the destructor for owner is guaranteed to be run prior to the function exiting. The memory has been freed. Had we used a raw pointer, the return statement would have skipped over any future delete statement, for example, in the body of main, that would have performed the cleanup, resulting in a leak. Since the release mechanism is bound to the object's destructor, we can never miss cleanup, regardless of how the scope is exited.

We can see how a bare pointer lacks such a guarantee:

#include <iostream>

int main()
{
    int* ptr{ new int{ 7 } };

    std::cout << *ptr << '\n';

    // If an early return occurred here, the delete would be skipped

    delete ptr;

    return 0;
}

The delete has not yet been executed, and if we exit before reaching it by any means, the memory that we have allocated will not be reclaimed. In RAII, this cannot happen; the destructor is guaranteed to be called.

Also, the ownership is clear. owner truly owns its int. It remains an owner of that int for its entire lifetime, which coincides with the lifetime of the owner object. As soon as owner is destroyed and its destructor called, its ownership and access to the memory has ended and the memory is released. No stray pointer can be lurking in the environment to "own" the same memory.

Exercise: Introduction to RAIIStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: Introduction to RAII

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Place IntOwner in a nested scope, report when its destructor releases the int, and print a message after the scope. Disable copying of this simple owning class.

Mission checklist

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. Compile and run your answer, then compare with the worked solution and explain the result.

Starter program

This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.

#include <iostream>

class IntOwner
{
public:
    IntOwner(int value)
        : ptr{ new int{ value } }
    {
    }

    IntOwner(const IntOwner&) = delete;
    IntOwner& operator=(const IntOwner&) = delete;

    ~IntOwner()
    {
        delete ptr;
    }

    int get() const
    {
        return *ptr;
    }

private:
    int* ptr;
};

int main()
{
    IntOwner owner{ 42 };
    std::cout << owner.get() << '\n';

    return 0;
}

Hints

Hint 1

IntOwner acquires its int in the constructor and deletes it in the destructor. Find the scope exit that triggers cleanup for owner automatically.

Hint 2

Use a nested pair of braces to trigger destruction before a later output statement. Delete the copy constructor and copy assignment to prevent sharing this owning raw pointer.

Worked solution

The owner is placed in an inner block and its destructor reports release. On leaving that block the destructor deletes the int before the After block message. Copying is explicitly disabled so this simple RAII owner cannot accidentally share its raw pointer.

#include <iostream>
class IntOwner
{
    int* ptr;
public:
    explicit IntOwner(int value) : ptr{new int{value}} {}
    IntOwner(const IntOwner&) = delete;
    IntOwner& operator=(const IntOwner&) = delete;
    ~IntOwner() { delete ptr; std::cout << "Int released\n"; }
    int get() const { return *ptr; }
};
int main()
{
    {
        IntOwner owner{42};
        std::cout << owner.get() << '\n';
    }
    std::cout << "After block\n";
}

Example output

42
Int released
After block

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.