🎓 A1Learner
🔍
0 of 34 lessons completed

C++ RAII

RAII is a pattern where an object acquires a resource in its constructor and releases that resource in its destructor. The release runs when the object leaves scope, even when a function returns early.

Manual locking misses the release

The job function takes the lock and then returns early because the job is not ready. The unlock line sits after the return, so it never runs.

#include <iostream>

void runJob() {
  std::cout << "Locked\n";
  std::cout << "Checking the job\n";
  bool ready = false;
  if (!ready) {
    return;
  }
  std::cout << "Job done\n";
  std::cout << "Unlocked\n";
}

int main() {
  runJob();
  std::cout << "Program ended\n";
  return 0;
}

A guard releases on the early return

The guard takes the lock in its constructor and releases it in its destructor. The same early return now prints the unlock line before main continues.

#include <iostream>

class LockGuard {
public:
  LockGuard() {
    std::cout << "Locked\n";
  }
  ~LockGuard() {
    std::cout << "Unlocked\n";
  }
};

void runJob() {
  LockGuard guard;
  std::cout << "Checking the job\n";
  bool ready = false;
  if (!ready) {
    return;
  }
  std::cout << "Job done\n";
}

int main() {
  runJob();
  std::cout << "Program ended\n";
  return 0;
}

Nested guards release in reverse order

The outer guard is made before the inner guard. The inner guard leaves scope first, so its unlock line prints first.

#include <iostream>
#include <string>

class NamedGuard {
public:
  std::string name;
  NamedGuard(std::string label) {
    name = label;
    std::cout << "Locked: " << name << "\n";
  }
  ~NamedGuard() {
    std::cout << "Unlocked: " << name << "\n";
  }
};

int main() {
  {
    NamedGuard outer("Outer");
    {
      NamedGuard inner("Inner");
      std::cout << "Inner work\n";
    }
    std::cout << "Outer work\n";
  }
  std::cout << "All done\n";
  return 0;
}

Example

#include <iostream>

class PrintGuard {
public:
  PrintGuard() {
    std::cout << "Printer reserved\n";
  }
  ~PrintGuard() {
    std::cout << "Printer released\n";
  }
};

int main() {
  {
    PrintGuard guard;
    std::cout << "Printing the report\n";
    std::cout << "Printing the labels\n";
  }
  std::cout << "Next task\n";
  return 0;
}

Key points

  • RAII stands for Resource Acquisition Is Initialization.
  • A guard object acquires its resource in its constructor.
  • The same object releases the resource in its destructor.
  • A destructor runs when a local object leaves scope.
  • An early return still runs the destructor of every local object in the scope.
  • Guards in nested scopes release in reverse order of construction.

In this example, a PrintGuard object reserves the printer in its constructor. The two job lines print while the guard lives. The guard leaves scope after the second job, so its destructor prints the release line before the next task line.

Note

A plain class with a constructor and a destructor can act as a guard.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A guard object is made inside a block. When does its destructor run?