Skip to main content

C++ Concepts

Resource Acquisition Is Initialization

RAII is a design pattern where resource lifetime is bound to object lifetime. When the object is created, it acquires the resource. When the object is destroyed, it releases the resource automatically via the destructor.

Resources Include : 

  • heap memory
  • file handles
  • sockets
  • mutex locks
  • db connections
  • GPU buffers
  • threads
File I/O RAII Example
#include <iostream>
#include <fstream>
#include <string>

// ifstream abides by RAII design pattern
// Resource (file) is acquired during scope
// Destructor naturally runs when escaping scope
int main() {
  std::ifstream file{"file.txt"};
  if (file.isOpen()) {
    std::cout << "file is open" << std::endl;
  } else {
    std::cout << "file is close" << std::endl;
  }  
}
Mutex RAII Example
std::mutex m;

// No RAII object.
// if some condition is true, m.unlock never runs -> deadlock
void foo() {
  m.lock();

  if (some condition) {
    return;
  }

  m.unlock();
}

// With RAII object
// destructor runs when leaving scope
void bar() {
  std::lock_guard<std::mutex> lock(m);

  if (some condition) return;
}

lvalue vs rvalue

lvalue - An expression that refers to a persistent object with an identifiable memory location.

// persists
// has address in memory
int x = 10;

value - A temporary expression that does not persist beyond the full expression.

// temporary
10;
std::string("hello");
foo();

 

Rule of 5

    Destructor Copy constructor Copy assignment Move constructor Move assignment

    We use the rule of 5 : when a class owns a resource, the default copy behavior is unsafe, so we must explicitly define copy and move semantics

    class Buffer {
      // std::vector already implements RAII
      // RAII container
      std::vector<int> data;
    };

    Raw pointers are mainly used for non-owning references

    Copy or Move
    // double delete error
    std::unique_ptr<int> p1 = std::make_unique<int>(5);
    std::unique_ptr<int> p2 = p1;
    
    // if this is allowed, it could cause double frees/memory corruption/unexpected behavior
      copy constructor tries to run because p1 is an lvalue std::unique_ptr deletes its copy constructor compilation fails

      You have to move ownership

      // unique_ptr is move only
      std::unique_ptr<int> p2 = std::move(p1);

      Move Semantics

       

      // function f takes ownership
      // simplest - forces move
      void f(std::unique_ptr<int> p);
        // but not
        std::unique_ptr<int> p = std::make_unique<inst>(5);
        f(p);
        // it tries to copy p but there is no copy constructor
      
      // function g is only BORROWING access
      // cannot modify pointer
      void g(const std::unique_ptr<int>& p);
      
      // function h borrows mutable
      void h(std::unique_ptr<int>& p);
      
      // function i expects something moveable
      // Caller must pass temporary or std::move
      void i(std::unique_ptr<int>&& p);