Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. C++ Dictionary
  3. tips

tips

C++ has many established idioms for improving code safety, maintainability, and extensibility. This page covers five techniques commonly used in real-world development: RAII, Pimpl, CRTP, Copy-and-Swap, and Scope Guard.

Technique Reference

TechniquePurpose / Overview
RAIIResource Acquisition Is Initialization. Ties resource acquisition to object construction and releases resources automatically in the destructor. The foundation of exception-safe resource management.
Pimpl IdiomPointer to Implementation. Hides implementation details behind a pointer, reducing header dependencies and shortening compile times.
CRTPCuriously Recurring Template Pattern. Achieves static polymorphism without virtual functions by passing the derived class itself as a template argument to the base class.
Copy-and-SwapImplements the copy assignment operator using "copy constructor + swap". Satisfies both exception safety and self-assignment safety at the same time.
Scope GuardAn object that executes cleanup code when a scope is exited (whether normally or via exception). A generalized application of the RAII pattern.

RAII

// ========================================
// RAII (Resource Acquisition Is Initialization) basic syntax
// ========================================

// Acquire the resource in the constructor,
// release it automatically in the destructor
class ResourceHandle {
    int* data;
public:
    // Resource acquisition (happens at object construction)
    explicit ResourceHandle(int size) : data(new int[size]) {}

    // Destructor releases the resource
    // Called reliably during stack unwinding even if an exception occurs
    ~ResourceHandle() {
        delete[] data;
    }

    // Copy disabled (prevents double-free)
    ResourceHandle(const ResourceHandle&) = delete;
    ResourceHandle& operator=(const ResourceHandle&) = delete;
};

// delete[] is called automatically when the scope ends
void example() {
    ResourceHandle handle(100);  // executes new int[100]
    // even if an exception is thrown, ~ResourceHandle() is reliably called
}  // <- delete[] is called automatically here

Pimpl Idiom

// ========================================
// Pimpl idiom basic syntax
// ========================================

// Header file (public interface)
// Impl definition is not included in the header, so no extra header dependencies
class LabMember {
public:
    LabMember();
    ~LabMember();
    void doWork();
private:
    struct Impl;           // forward declaration only
    Impl* pImpl;           // pointer to the implementation
};

// .cpp file (implementation details)
// Only this file depends on the implementation-detail headers
struct LabMember::Impl {
    std::string name;
    int labNumber;
    // ... members that require heavy headers can be placed here
};

LabMember::LabMember() : pImpl(new Impl()) {}
LabMember::~LabMember() { delete pImpl; }
void LabMember::doWork() { /* use pImpl->name */ }

CRTP

// ========================================
// CRTP (Curiously Recurring Template Pattern) basic syntax
// ========================================

// Pass the derived class itself as the template argument to the base class
template <typename Derived>
class Base {
public:
    // Call the derived class method without virtual functions
    void interface() {
        // cast to the derived type with static_cast before calling
        static_cast<Derived*>(this)->implementation();
    }
};

// The derived class passes itself as the type argument
class ConcreteA : public Base<ConcreteA> {
public:
    void implementation() {
        std::cout << "ConcreteA implementation" << std::endl;
    }
};

Copy-and-Swap

// ========================================
// Copy-and-Swap idiom basic syntax
// ========================================

class MyClass {
    int* data;
    int size;
public:
    // Copy constructor (performs a deep copy)
    MyClass(const MyClass& other) : size(other.size), data(new int[other.size]) {
        std::copy(other.data, other.data + size, data);
    }

    // swap function (noexcept so it never throws)
    friend void swap(MyClass& a, MyClass& b) noexcept {
        std::swap(a.data, b.data);
        std::swap(a.size, b.size);
    }

    // Assignment operator: make a copy by value, then swap
    // Achieves exception safety (strong guarantee) and self-assignment safety
    MyClass& operator=(MyClass other) {  // copy is made by value
        swap(*this, other);              // swap the contents
        return *this;
    }  // <- the copy (old contents) is automatically released here

    ~MyClass() { delete[] data; }
};

Scope Guard

// ========================================
// Scope Guard basic syntax
// ========================================

#include <functional>

// A class that always runs a function when the scope exits
class ScopeGuard {
    std::function<void()> cleanup;
public:
    explicit ScopeGuard(std::function<void()> f) : cleanup(f) {}
    ~ScopeGuard() { cleanup(); }  // runs whether exit is normal or via exception

    // Copy disabled (prevents double execution)
    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;
};

void example() {
    // "cleanup" is guaranteed to run when the scope exits
    ScopeGuard guard([]() { std::cout << "running cleanup" << std::endl; });
    // ... even if an exception is thrown here, the guard's destructor is reliably called
}

Sample Code

sg_lab_tips.cpp
// ========================================
// sg_lab_tips.cpp
// Using Steins;Gate lab members as a theme,
// this sample demonstrates RAII, Pimpl, CRTP,
// Copy-and-Swap, and Scope Guard in one program.
// ========================================

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <memory>

// ============================================================
// 1. Scope Guard
//    Runs cleanup when the scope exits (normal or exception)
// ============================================================
class ScopeGuard {
    std::function<void()> cleanup;
    bool dismissed;  // dismiss flag (set when cancellation is desired)
public:
    explicit ScopeGuard(std::function<void()> f)
        : cleanup(f), dismissed(false) {}

    // Runs cleanup when the scope exits
    ~ScopeGuard() {
        if (!dismissed) {
            cleanup();
        }
    }

    // Cancels the cleanup (call when success is confirmed)
    void dismiss() { dismissed = true; }

    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;
};

// ============================================================
// 2. RAII
//    Manages the "world-line interference log" resource with RAII.
//    The acquired resource is reliably released in the destructor.
// ============================================================
class WorldLineLog {
    std::string* entries;  // log array on the heap
    int capacity;
    int count;

public:
    explicit WorldLineLog(int cap)
        : capacity(cap), count(0), entries(new std::string[cap]) {
        std::cout << "[RAII] WorldLineLog allocated (capacity: " << cap << ")" << std::endl;
    }

    // Destructor reliably releases memory (even on exception)
    ~WorldLineLog() {
        delete[] entries;
        std::cout << "[RAII] WorldLineLog released (" << count << " entries)" << std::endl;
    }

    void addEntry(const std::string& msg) {
        if (count < capacity) {
            entries[count++] = msg;
        }
    }

    void showAll() const {
        for (int i = 0; i < count; i++) {
            std::cout << "  [" << (i + 1) << "] " << entries[i] << std::endl;
        }
    }

    // Copy disabled (prevents double-free)
    WorldLineLog(const WorldLineLog&) = delete;
    WorldLineLog& operator=(const WorldLineLog&) = delete;
};

// ============================================================
// 3. Pimpl Idiom
//    Hides LabMember implementation details in an Impl struct.
//    Reduces header dependencies and shortens compile time.
// ============================================================
class LabMember {
    // forward declaration: implementation details are not exposed in the header
    struct Impl {
        std::string name;
        int labMemberNumber;
        std::string role;

        Impl(const std::string& n, int num, const std::string& r)
            : name(n), labMemberNumber(num), role(r) {}
    };

    Impl* pImpl;  // pointer to the implementation

public:
    LabMember(const std::string& name, int number, const std::string& role)
        : pImpl(new Impl(name, number, role)) {}

    // Copy constructor (performs a deep copy)
    LabMember(const LabMember& other)
        : pImpl(new Impl(*other.pImpl)) {}

    // swap function (used by Copy-and-Swap)
    friend void swap(LabMember& a, LabMember& b) noexcept {
        std::swap(a.pImpl, b.pImpl);
    }

    // 4. Copy-and-Swap idiom
    //    Pass by value (copy), then swap.
    //    Satisfies exception safety (strong guarantee) and self-assignment safety.
    LabMember& operator=(LabMember other) {  // copy made by value
        swap(*this, other);                   // swap the pointers
        return *this;
    }  // <- other (old pImpl) is automatically deleted here

    ~LabMember() {
        delete pImpl;
    }

    // Accessors (implementation details are hidden inside pImpl)
    const std::string& getName()   const { return pImpl->name; }
    int                getNumber() const { return pImpl->labMemberNumber; }
    const std::string& getRole()   const { return pImpl->role; }
};

// ============================================================
// 5. CRTP (Curiously Recurring Template Pattern)
//    Achieves static polymorphism without virtual functions.
//    No runtime dispatch cost; dispatch is resolved at compile time.
// ============================================================
template <typename Derived>
class LabActivity {
public:
    // Calls the derived class's implement() without virtual functions
    void execute() {
        std::cout << "[CRTP] ";
        static_cast<Derived*>(this)->implement();
    }

    // Common pre/post processing can be added for all derived classes
    void executeWithLog() {
        std::cout << "  -- task start --" << std::endl;
        execute();
        std::cout << "  -- task end --" << std::endl;
    }
};

// Okabe Rintaro's activity (directing the time-machine experiment)
class OkabeActivity : public LabActivity<OkabeActivity> {
public:
    void implement() {
        std::cout << "Okabe Rintaro: Directing the D-mail transmission experiment." << std::endl;
    }
};

// Makise Kurisu's activity (researching the memory-transfer experiment)
class KurisuActivity : public LabActivity<KurisuActivity> {
public:
    void implement() {
        std::cout << "Makise Kurisu: Designing the memory-transfer experiment protocol." << std::endl;
    }
};

// Amane Suzuha's activity (servicing the time machine)
class SuzuhaActivity : public LabActivity<SuzuhaActivity> {
public:
    void implement() {
        std::cout << "Amane Suzuha: Servicing the time machine in C204." << std::endl;
    }
};

// ============================================================
// Main function
// ============================================================
int main() {

    // --------------------------------------------------
    // RAII demo: resource is released automatically when scope ends
    // --------------------------------------------------
    std::cout << "=== RAII Demo ===" << std::endl;
    {
        WorldLineLog log(10);  // heap allocation (constructor)

        log.addEntry("World line divergence: 1.048596 (beta world line)");
        log.addEntry("Okabe Rintaro sends a D-mail to the past");
        log.addEntry("Confirmed Makise Kurisu's survival");

        std::cout << "World-line interference log:" << std::endl;
        log.showAll();

    }  // <- ~WorldLineLog() is called automatically here
    std::cout << std::endl;

    // --------------------------------------------------
    // Scope Guard demo: cleanup runs reliably
    // --------------------------------------------------
    std::cout << "=== Scope Guard Demo ===" << std::endl;
    {
        std::cout << "Starting time-leap experiment..." << std::endl;

        // "experiment end" is guaranteed to print when the scope exits
        ScopeGuard experimentEnd([]() {
            std::cout << "[Scope Guard] Time-leap experiment ended. Log saved." << std::endl;
        });

        std::cout << "Shiina Mayuri: \"Okarin, everything's ready~!\"" << std::endl;
        std::cout << "Okabe Rintaro: Executing time leap..." << std::endl;

        // experimentEnd's destructor is called here
    }
    std::cout << std::endl;

    // --------------------------------------------------
    // Pimpl + Copy-and-Swap demo
    // --------------------------------------------------
    std::cout << "=== Pimpl + Copy-and-Swap Demo ===" << std::endl;

    LabMember okabe("Okabe Rintaro",  1, "Lab leader, inventor");
    LabMember kurisu("Makise Kurisu", 4, "Theoretical physics");
    LabMember mayuri("Shiina Mayuri", 2, "Cosplay production");
    LabMember suzuha("Amane Suzuha",  8, "Time-machine maintenance");
    LabMember hashida("Hashida Itaru", 3, "Hacking");

    std::cout << "Lab member registration:" << std::endl;

    // manage lab members in a vector
    std::vector<LabMember*> members;
    members.push_back(&okabe);
    members.push_back(&kurisu);
    members.push_back(&mayuri);
    members.push_back(&suzuha);
    members.push_back(&hashida);

    for (int i = 0; i < (int)members.size(); i++) {
        std::cout << "  Member " << members[i]->getNumber()
                  << ": " << members[i]->getName()
                  << " (" << members[i]->getRole() << ")" << std::endl;
    }

    // Verify Copy-and-Swap (assignment operator)
    std::cout << std::endl << "Copy-and-Swap (assignment) check:" << std::endl;
    LabMember copy = okabe;  // copy constructor
    std::cout << "  Before assignment: " << copy.getName() << std::endl;
    copy = kurisu;            // Copy-and-Swap assignment operator
    std::cout << "  After assignment:  " << copy.getName() << std::endl;
    std::cout << std::endl;

    // --------------------------------------------------
    // CRTP demo: static polymorphism without virtual functions
    // --------------------------------------------------
    std::cout << "=== CRTP Demo ===" << std::endl;

    OkabeActivity  okabeAct;
    KurisuActivity kurisuAct;
    SuzuhaActivity suzuhaAct;

    okabeAct.executeWithLog();
    kurisuAct.executeWithLog();
    suzuhaAct.executeWithLog();
    std::cout << std::endl;

    std::cout << "El Psy Kongroo." << std::endl;

    return 0;
}
# Compile
g++ -std=c++17 sg_lab_tips.cpp -o sg_lab_tips && ./sg_lab_tips
=== RAII Demo ===
[RAII] WorldLineLog allocated (capacity: 10)
World-line interference log:
  [1] World line divergence: 1.048596 (beta world line)
  [2] Okabe Rintaro sends a D-mail to the past
  [3] Confirmed Makise Kurisu's survival
[RAII] WorldLineLog released (3 entries)

=== Scope Guard Demo ===
Starting time-leap experiment...
Shiina Mayuri: "Okarin, everything's ready~!"
Okabe Rintaro: Executing time leap...
[Scope Guard] Time-leap experiment ended. Log saved.

=== Pimpl + Copy-and-Swap Demo ===
Lab member registration:
  Member 1: Okabe Rintaro (Lab leader, inventor)
  Member 4: Makise Kurisu (Theoretical physics)
  Member 2: Shiina Mayuri (Cosplay production)
  Member 8: Amane Suzuha (Time-machine maintenance)
  Member 3: Hashida Itaru (Hacking)

Copy-and-Swap (assignment) check:
  Before assignment: Okabe Rintaro
  After assignment:  Makise Kurisu

=== CRTP Demo ===
  -- task start --
[CRTP] Okabe Rintaro: Directing the D-mail transmission experiment.
  -- task end --
  -- task start --
[CRTP] Makise Kurisu: Designing the memory-transfer experiment protocol.
  -- task end --
  -- task start --
[CRTP] Amane Suzuha: Servicing the time machine in C204.
  -- task end --

El Psy Kongroo.

Common Mistakes

RAII and Pimpl are simple idioms, but there are subtle pitfalls to be aware of.

Exception-safety pitfall in RAII constructors

When a RAII class acquires multiple resources in its constructor, if an exception is thrown after the first acquisition but before the second, the first resource leaks because the destructor is not called for an incompletely constructed object. Using smart pointers makes each resource an independent RAII object and prevents this leak.

NG
#include <iostream>
#include <memory>
#include <stdexcept>

// NG: holding multiple raw pointers in a constructor risks leaks
class BadResource {
    int* buffer1;
    int* buffer2;
public:
    BadResource() {
        buffer1 = new int[100];  // succeeds
        // if an exception is thrown here, buffer1 leaks
        buffer2 = new int[200];  // bad_alloc may be thrown here
    }
    ~BadResource() {
        delete[] buffer1;
        delete[] buffer2;
        // destructor is not called if the constructor did not complete
    }
};
OK
// OK: using smart pointers makes each resource an independent RAII object
class GoodResource {
    std::unique_ptr<int[]> buffer1;
    std::unique_ptr<int[]> buffer2;
public:
    GoodResource()
        : buffer1(new int[100])
        , buffer2(new int[200])  // if an exception is thrown here, buffer1 is auto-released
    {}
    // no destructor needed (unique_ptr handles release automatically)
};

int main() {
    // GoodResource is exception-safe
    try {
        GoodResource r;
        std::cout << "Okabe Rintaro: resource acquisition complete. El Psy Kongroo." << std::endl;
    } catch (const std::bad_alloc& e) {
        std::cout << "Memory allocation failed: " << e.what() << std::endl;
    }
    return 0;
}

Copy and move semantics with Pimpl

For a class that uses Pimpl, the compiler-generated default copy constructor and copy assignment operator perform a shallow copy of the pointer, leading to a double-free. If copy semantics are needed, implement a deep copy; if not, disable them with = delete. It is also recommended to explicitly define move operations.

NG: default copy causes double-free
#include <iostream>
#include <string>

// NG: the default copy makes a shallow copy of the pointer, causing a double-free
class BadPimpl {
    struct Impl { std::string name; };
    Impl* pImpl;
public:
    explicit BadPimpl(const std::string& n) : pImpl(new Impl{n}) {}
    ~BadPimpl() { delete pImpl; }
    // no copy constructor or assignment operator defined,
    // so the default shallow copy is used -> risk of double-free
};
OK: disable copy or implement deep copy
// OK: define copy as a deep copy, or disable it with = delete
class GoodPimplNoCopy {
    struct Impl { std::string name; };
    Impl* pImpl;
public:
    explicit GoodPimplNoCopy(const std::string& n) : pImpl(new Impl{n}) {}
    ~GoodPimplNoCopy() { delete pImpl; }

    // Copy disabled (sufficient when copying is not needed)
    GoodPimplNoCopy(const GoodPimplNoCopy&) = delete;
    GoodPimplNoCopy& operator=(const GoodPimplNoCopy&) = delete;

    // Move is allowed (transfers ownership)
    GoodPimplNoCopy(GoodPimplNoCopy&& other) noexcept
        : pImpl(other.pImpl) {
        other.pImpl = nullptr;
    }
    GoodPimplNoCopy& operator=(GoodPimplNoCopy&& other) noexcept {
        if (this != &other) {
            delete pImpl;
            pImpl = other.pImpl;
            other.pImpl = nullptr;
        }
        return *this;
    }

    const std::string& getName() const { return pImpl->name; }
};

int main() {
    GoodPimplNoCopy a("Makise Kurisu");
    GoodPimplNoCopy b = std::move(a);  // move is OK
    std::cout << "After move: " << b.getName() << std::endl;
    // GoodPimplNoCopy c = b;  // copy is disabled, so this is a compile error
    return 0;
}

Overview

RAII (Resource Acquisition Is Initialization) is the most important idiom in C++. By acquiring resources (memory, files, sockets, etc.) in the constructor and releasing them in the destructor, resources are reliably freed during stack unwinding even when exceptions are thrown. All of the standard library's std::unique_ptr and std::lock_guard are designed on the RAII principle.

The Pimpl Idiom (Pointer to Implementation) cuts header dependencies by keeping implementation details out of header files, reducing compile times. Because changing the implementation does not change the header, it also contributes to ABI stability in libraries. See also Classes.

CRTP (Curiously Recurring Template Pattern) passes the derived class itself as the template argument of the base class, achieving static polymorphism without the virtual keyword. There is no vtable lookup cost and dispatch is resolved at compile time, yielding high performance.

The Copy-and-Swap Idiom implements the copy assignment operator simply by "create a temporary object with the copy constructor, then swap the contents". It achieves both the strong exception-safety guarantee and self-assignment safety with minimal code. See also Copy and Assignment.

Scope Guard is a generalization of RAII that accepts an arbitrary cleanup lambda in its constructor and executes it in the destructor when the scope is exited. As of C++17, std::scope_exit (Library Fundamentals TS) is under discussion for standardization. For now, a simple hand-written implementation like the one in the sample above is commonly used.

If you find any errors or copyright issues, please .