Skip to content

Instantly share code, notes, and snippets.

@MangaD
Created June 24, 2025 16:28
Show Gist options
  • Select an option

  • Save MangaD/d0d46578a376d200b642ac94797d9120 to your computer and use it in GitHub Desktop.

Select an option

Save MangaD/d0d46578a376d200b642ac94797d9120 to your computer and use it in GitHub Desktop.
C++ Idioms

C++ Idioms

CC0

Disclaimer: Grok generated document.

C++ idioms are recurring patterns or techniques used by experienced programmers to write robust, efficient, and maintainable code. They leverage C++'s features to solve common problems or express intent clearly. Below is a comprehensive overview of key C++ idioms, organized by their purpose and functionality. This list covers both classic idioms and modern ones introduced with evolving C++ standards (up to C++20 and some C++23 insights where relevant). I’ll keep the explanations concise yet informative, focusing on the idiom, its purpose, and a brief example where necessary.

1. Resource Management Idioms

These idioms ensure proper resource handling, preventing leaks and ensuring exception safety.

RAII (Resource Acquisition Is Initialization)

  • Purpose: Manages resources (memory, files, sockets, etc.) by tying their lifetime to the scope of an object.
  • How It Works: Resources are acquired in a constructor and released in a destructor, ensuring automatic cleanup.
  • Example:
    class FileHandle {
        FILE* file;
    public:
        FileHandle(const char* name) : file(fopen(name, "r")) {}
        ~FileHandle() { if (file) fclose(file); }
    };
    // Usage: FileHandle fh("example.txt"); // Automatically closes when fh goes out of scope
  • Modern Note: Used extensively with smart pointers (std::unique_ptr, std::shared_ptr).

Scope Guard

  • Purpose: Ensures cleanup actions occur when exiting a scope, even if exceptions are thrown.
  • How It Works: A guard object executes a cleanup function in its destructor.
  • Example:
    struct ScopeGuard {
        std::function<void()> cleanup;
        ScopeGuard(std::function<void()> f) : cleanup(f) {}
        ~ScopeGuard() { cleanup(); }
    };
    void example() {
        FILE* f = fopen("file.txt", "w");
        ScopeGuard guard([&] { fclose(f); });
        // File closed automatically on scope exit
    }
  • Modern Note: C++20’s std::unique_resource (proposed) or libraries like folly::ScopeGuard simplify this.

Non-Copyable (Disable Copying)

  • Purpose: Prevents accidental copying of objects that manage unique resources.
  • How It Works: Delete copy constructor and assignment operator.
  • Example:
    class NonCopyable {
    public:
        NonCopyable() = default;
        NonCopyable(const NonCopyable&) = delete;
        NonCopyable& operator=(const NonCopyable&) = delete;
    };
  • Modern Note: Often used with std::unique_ptr or in classes like mutexes.

2. Object-Oriented Idioms

These idioms structure classes and their interactions for better encapsulation and extensibility.

Pimpl (Pointer to Implementation)

  • Purpose: Hides implementation details, reduces compilation dependencies, and improves binary compatibility.
  • How It Works: Forward-declare a private implementation class and hold a pointer to it.
  • Example:
    class Widget {
        struct Impl;
        std::unique_ptr<Impl> pImpl;
    public:
        Widget();
        ~Widget();
        void doSomething();
    };
  • Modern Note: Reduces header pollution and compile times in large projects.

Virtual Destructor

  • Purpose: Ensures proper cleanup of derived class objects through base class pointers.
  • How It Works: Declare the base class destructor as virtual.
  • Example:
    class Base {
    public:
        virtual ~Base() = default;
    };
    class Derived : public Base { /* resources */ };
  • Modern Note: Essential for polymorphic base classes to avoid undefined behavior.

Non-Virtual Interface (NVI)

  • Purpose: Provides a stable public interface while allowing derived classes to customize behavior.
  • How It Works: Public non-virtual functions call private/protected virtual functions.
  • Example:
    class Interface {
    public:
        void doWork() { doWorkImpl(); } // Public non-virtual
    private:
        virtual void doWorkImpl() = 0; // Derived classes override
    };
  • Modern Note: Enhances control over the interface and ensures invariants.

3. Memory Management Idioms

These idioms optimize or simplify memory usage.

Copy-and-Swap

  • Purpose: Implements strong exception safety for assignment operators.
  • How It Works: Assignment operator takes a copy by value, then swaps with the current object.
  • Example:
    class MyClass {
        int* data;
    public:
        MyClass& operator=(MyClass other) {
            swap(*this, other);
            return *this;
        }
        friend void swap(MyClass& a, MyClass& b) noexcept {
            using std::swap;
            swap(a.data, b.data);
        }
    };
  • Modern Note: Simplifies exception-safe assignment with std::swap.

Small Object Optimization

  • Purpose: Avoids dynamic allocation for small objects to improve performance.
  • How It Works: Store small objects inline (e.g., in a union) instead of on the heap.
  • Example: Used in std::string (short string optimization) or std::function.
  • Modern Note: Common in high-performance libraries like std::variant.

4. Type Safety and Generic Programming Idioms

These idioms leverage C++’s type system for safety and flexibility.

Type Erasure

  • Purpose: Allows handling different types uniformly without knowing their concrete types.
  • How It Works: Wraps types in a common interface, often using a base class or std::function.
  • Example:
    class Any {
        struct Base { virtual ~Base() {} };
        template<typename T>
        struct Derived : Base { T value; };
        std::unique_ptr<Base> ptr;
    public:
        template<typename T>
        Any(T v) : ptr(new Derived<T>{v}) {}
    };
  • Modern Note: Used in std::any, std::function, and similar constructs.

CRTP (Curiously Recurring Template Pattern)

  • Purpose: Enables static polymorphism, avoiding virtual function overhead.
  • How It Works: A base class template takes the derived class as a parameter.
  • Example:
    template<typename Derived>
    class Base {
    public:
        void interface() { static_cast<Derived*>(this)->implementation(); }
    };
    class Derived : public Base<Derived> {
        void implementation() { /* ... */ }
    };
  • Modern Note: Used in performance-critical code and for static dispatch.

Tag Dispatch

  • Purpose: Selects function overloads based on type traits or tags.
  • How It Works: Pass a tag type to disambiguate overloads.
  • Example:
    template<typename Iterator>
    void process(Iterator begin, Iterator end, std::random_access_iterator_tag) {
        // Optimized for random access
    }
    template<typename Iterator>
    void process(Iterator begin, Iterator end) {
        process(begin, end, typename std::iterator_traits<Iterator>::iterator_category{});
    }
  • Modern Note: Common in STL algorithms and generic libraries.

5. Error Handling Idioms

These idioms manage errors robustly.

Exception-Safe Assignment (See Copy-and-Swap)

  • Covered above; ensures strong exception guarantee in assignments.

Guarded Suspension

  • Purpose: Temporarily suspends execution until a condition is met, often for error handling.
  • How It Works: Use condition variables or similar constructs.
  • Example:
    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;
    void waitForWork() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return ready; });
    }
  • Modern Note: Common in concurrent error handling with std::condition_variable.

6. Performance Idioms

These idioms optimize code for speed or resource usage.

Move Semantics

  • Purpose: Avoids unnecessary copying of temporary objects.
  • How It Works: Use move constructors and move assignment operators.
  • Example:
    class MyClass {
        std::vector<int> data;
    public:
        MyClass(MyClass&& other) noexcept : data(std::move(other.data)) {}
    };
  • Modern Note: Central to C++11 and later, used with std::move.

Inline Namespace

  • Purpose: Manages versioning or ABI compatibility without breaking existing code.
  • How It Works: Place code in an inline namespace for seamless access.
  • Example:
    namespace MyLib {
        inline namespace v1 {
            void func() { /* Version 1 */ }
        }
    }
  • Modern Note: Useful in libraries to evolve APIs.

7. Miscellaneous Idioms

These idioms address specific design or usage patterns.

Factory Method

  • Purpose: Creates objects without specifying their exact class.
  • How It Works: A static or virtual function returns a base class pointer or reference.
  • Example:
    class Product {
    public:
        virtual ~Product() = default;
        static std::unique_ptr<Product> create();
    };
  • Modern Note: Often combined with std::unique_ptr for ownership semantics.

Singleton (Thread-Safe)

  • Purpose: Ensures a single instance of a class exists.
  • How It Works: Static instance with lazy initialization, often using std::call_once.
  • Example:
    class Singleton {
        static Singleton* instance;
        static std::once_flag flag;
    public:
        static Singleton& getInstance() {
            std::call_once(flag, [] { instance = new Singleton(); });
            return *instance;
        }
    private:
        Singleton() {}
    };
  • Modern Note: C++11’s static initialization or std::call_once ensures thread safety.

Policy-Based Design

  • Purpose: Customizes class behavior through template parameters.
  • How It Works: Pass policy classes as template arguments to configure functionality.
  • Example:
    template<typename StoragePolicy>
    class Container : private StoragePolicy {
        // Use StoragePolicy for storage operations
    };
  • Modern Note: Common in generic libraries like Boost.

8. Modern C++ Idioms (C++11 and Beyond)

These idioms leverage newer C++ features for clarity and efficiency.

Immediately Invoked Function Expression (IIFE)

  • Purpose: Initializes complex objects in a single expression.
  • How It Works: Use a lambda that is immediately invoked.
  • Example:
    auto value = [] {
        std::vector<int> v;
        // Complex initialization
        return v;
    }();
  • Modern Note: Common in C++11+ for concise initialization.

Constrained Templates (Concepts in C++20)

  • Purpose: Restricts template parameters to specific types or properties.
  • How It Works: Use concept or requires clauses.
  • Example:
    template<typename T>
    concept Numeric = std::is_arithmetic_v<T>;
    template<Numeric T>
    T add(T a, T b) { return a + b; }
  • Modern Note: Improves template error messages and code clarity.

Fold Expressions

  • Purpose: Simplifies variadic template operations.
  • How It Works: Use ... to apply an operator over a parameter pack.
  • Example:
    template<typename... Args>
    auto sum(Args... args) {
        return (args + ...);
    }
  • Modern Note: Introduced in C++17, reduces boilerplate in variadic templates.

Notes on Usage

  • Context Matters: Not all idioms are universally applicable. For example, RAII is ubiquitous, but CRTP is niche and best for performance-critical static polymorphism.
  • Modern C++ Preference: Prefer idioms that leverage C++11+ features (e.g., move semantics, smart pointers) over older patterns (e.g., raw pointers) for safety and performance.
  • Library Support: Many idioms are now part of the standard library (e.g., std::unique_ptr for RAII, std::any for type erasure), reducing the need for custom implementations.

If you want a deeper dive into any specific idiom, code examples, or analysis of how these are used in real-world projects, let me know! I can also search X or the web for recent discussions or provide a canvas for visualizing related concepts if needed.

@MangaD
Copy link
Author

MangaD commented Jun 27, 2025

@botangutan
Copy link

Non-Virtual Interface (NVI)

I think making the doWorkImpl private will cause a compilation errror.
Wasn't the intention for it to be protected?

private: /* "!here" */
virtual void doWorkImpl() = 0; // Derived classes override

@MangaD
Copy link
Author

MangaD commented Sep 11, 2025

@botangutan

That is a great question. Making doWorkImpl() private will not cause a compilation error.

See: https://godbolt.org/z/5of8PnK7d

Private members of a base class cannot be accessed by a derived class, but they are still part of the object, and private virtual functions can be overriden (but the base versions cannot be called).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment