Skip to content
c++-conventions logo

c++-conventions

C++ coding conventions and best practices for modern C++ development. Use when writing, reviewing, or refactoring C++ code to ensure consistency with project standards, const-correctness, RAII principles, and C++23 features.

heikopanjas/agent-skills0installs0stars

SKILL.md

Full skill instructions

C++ Coding Conventions

General Principles

  • Follow modern C++ best practices (C++23 standard preferred, C++17 minimum)
  • Use RAII principles for resource management
  • Prefer smart pointers (std::unique_ptr, std::shared_ptr) over raw pointers
  • Apply const-correctness throughout the codebase
  • Write self-documenting code with clear naming and structure
  • Keep functions focused and modular
  • Leverage the type system for compile-time safety
  • Ensure platform portability (Linux, macOS, Windows)

C++ Standard and Compatibility

  • Use C++23 standard when possible for latest features
  • Maintain C++17 minimum for broader compiler support
  • Use standard library features over custom implementations
  • Avoid compiler-specific extensions unless necessary
  • Test on multiple compilers (GCC, Clang, MSVC)
  • Use feature test macros for conditional compilation
  • Handle platform differences through standard mechanisms

Const Correctness

  • All input parameters should be const when not modified
  • Member functions that don't modify state should be const
  • Use const references for complex types in parameters
  • Apply const to return values when appropriate
  • Examples:
    • ✅ Correct: void SetTitle(const std::string& title);
    • ✅ Correct: std::string GetTitle() const;
    • ✅ Correct: const Data& GetData() const;
    • ❌ Incorrect: void SetTitle(std::string title); (unnecessary copy)

Comparison Conventions

  • Always place constants on the left side of comparisons (constant-left style)
  • Use explicit nullptr comparisons instead of implicit boolean conversion
  • This prevents accidental assignment when = is used instead of ==
  • Examples:
    • ✅ Correct: if (nullptr == ptr), if (0 == value), if (true == condition)
    • ❌ Incorrect: if (!ptr), if (ptr == nullptr), if (value == 0)
  • Apply to all comparisons including pointer checks, numeric values, and booleans
  • Benefits: Compiler error if = is mistakenly used instead of ==

RAII and Resource Management

  • Use RAII for all resource management (memory, files, locks, etc.)
  • Prefer smart pointers over raw pointers:
    • std::unique_ptr for exclusive ownership
    • std::shared_ptr for shared ownership
    • std::weak_ptr to break circular dependencies
  • Use standard containers instead of manual memory management
  • Examples:
    // Good: RAII with smart pointers
    auto data = std::make_unique<Data>();
    auto shared = std::make_shared<SharedObject>();
    
    // Good: RAII with containers
    std::vector<int> numbers;
    std::string text;
    
    // Avoid: Raw pointers requiring manual cleanup
    Data* data = new Data();  // Must remember to delete
    
  • Let destructors handle cleanup automatically

Classes and Destructors

  • All destructors should be virtual (even when deleted)
  • All abstract/​interface classes should have a protected virtual destructor
  • Use the Rule of Zero when possible (let compiler generate special members)
  • When implementing special members, follow the Rule of Five
  • Declare move constructor and move assignment operator when beneficial

File Organization

  • Header files (.h): Class declarations, inline functions, templates
  • Implementation files (.cpp): Method implementations, non-template code
  • Each class should have a separate header and implementation file
  • Filename must match the class name exactly (e.g., Driver class → Driver.h and Driver.cpp)
  • Header files go in include/ directory
  • Implementation files go in src/ directory
  • Exceptions:
    • Template classes may have implementation in header if needed
    • Tightly coupled class hierarchies (like AST nodes) may share files

Implementation Separation

  • Method implementations should be in .cpp files, not inline in headers
  • Reduces recompilation of dependencies when implementation changes
  • Only these may remain inline in headers:
    • Constructors (if trivial)
    • Destructors (if trivial)
    • One-line getters/​setters for performance
    • Template functions (required)
  • Prefer out-of-line implementations for better compilation times

Header File Structure

  • Include guard or #pragma once at top
  • Includes (system headers first, then project headers)
  • Forward declarations (to minimize includes)
  • Type definitions and aliases
  • Class declarations
  • Inline function definitions
  • Example:
    #ifndef __MYPROJECT_CLASS_H_INCL__
    #define __MYPROJECT_CLASS_H_INCL__
    
    #include <string>
    #include <vector>
    
    #include "BaseClass.h"
    
    // Forward declarations
    class Helper;
    
    class MyClass : public BaseClass
    {
    public:
        // ... class definition
    };
    
    #endif // __MYPROJECT_CLASS_H_INCL__
    

Implementation File Organization

  • Include corresponding header first
  • Include system headers
  • Include project headers
  • Anonymous namespace for file-local helpers
  • Class member function implementations
  • Example:
    #include "MyClass.h"
    
    #include <algorithm>
    #include <iostream>
    
    #include "Helper.h"
    
    namespace
    {
        // File-local helper functions
        void LocalHelper()
        {
            // ...
        }
    }
    
    // Class member implementations
    MyClass::MyClass()
    {
        // ...
    }
    

Class Structure and Scope Order

  • Always declare scopes in the order: public, protected, private
  • This makes the public interface immediately visible when reading class definitions
  • Group related members together within each section
  • Example:
    class MyClass
    {
    public:
        // Constructors and destructor
        MyClass();
        virtual ~MyClass();
    
        // Public interface
        void PublicMethod();
        int GetValue() const;
    
    protected:
        // Protected interface for derived classes
        virtual void ProtectedMethod();
    
    private:
        // Private implementation details
        void PrivateHelper();
        int privateData_;
        std::string privateName_;
    };
    

Naming Conventions

  • Types (classes, structs, enums, typedefs): Upper PascalCase (e.g., Episode, MediaType)
  • Functions/​methods: Upper PascalCase (e.g., GetTitle, SetDuration, ParseInput)
  • Variables and function parameters: camelCase (e.g., bufferSize, episodeCount)
  • Member variables: camelCase with underscore postfix (e.g., dataSize_, title_)
  • Constants: UPPER_SNAKE_CASE (e.g., MAX_EPISODE_LENGTH, DEFAULT_TIMEOUT)
  • Namespaces: lowercase (e.g., myproject, utils)
  • Template parameters: Single uppercase letter or PascalCase (e.g., T, ValueType)
  • Remove redundant prefixes from class names (e.g., use Model instead of P3Model)

Include Guards

  • Use format __PROJECT_CLASS_NAME_H_INCL__ where CLASS_NAME matches the class
  • Must start with project-specific prefix to identify namespace
  • Single word class: Driver → __MYPROJECT_DRIVER_H_INCL__
  • Multi-word class: TestTools → __MYPROJECT_TEST_TOOLS_H_INCL__
  • Insert underscore between each word in PascalCase class names
  • Example:
    #ifndef __MYPROJECT_DRIVER_H_INCL__
    #define __MYPROJECT_DRIVER_H_INCL__
    
    // Class declaration
    
    #endif // __MYPROJECT_DRIVER_H_INCL__
    

Alignment Pragmas

  • All header files must use 8-byte alignment for types using #pragma pack
  • Include alignment pragmas at the top and restore at the bottom
  • Use cross-compiler compatible pragmas for MSVC, GCC, and Clang:
    // At top of header (after include guard, before includes)
    #pragma pack(push, 8)
    
    // ... class declarations ...
    
    // At bottom of header (before closing include guard)
    #pragma pack(pop)
    

Namespaces

  • Use namespaces to organize code logically
  • Avoid using directives in headers (e.g., using namespace std;)
  • Use using declarations sparingly in implementation files
  • Prefer explicit namespace qualification for clarity
  • Use nested namespaces for hierarchical organization
  • Examples:
    namespace myproject
    {
        namespace utils
        {
            class Helper { };
        }
    
        class MainClass { };
    }
    
    // C++17 nested namespace syntax
    namespace myproject::utils
    {
        class Helper { };
    }
    

Function and Method Design

  • Keep functions short and focused on single responsibility
  • Use early returns to reduce nesting depth
  • Pass by const reference for complex types, by value for primitives
  • Use trailing return types when it improves clarity (e.g., with auto)
  • For intentionally unused parameters, use [[maybe_unused]] attribute or comment
  • Examples:
    // Good: Clear parameter passing
    void ProcessData(const std::vector<int>& data, int threshold);
    
    // Good: Trailing return type with auto
    auto GetValue() -> std::optional<int>;
    
    // Good: Unused parameter handling
    void Handler([[maybe_unused]] int eventType)
    {
        // Implementation doesn't use eventType
    }
    

Type Definitions and Aliases

  • Use using instead of typedef for type aliases
  • Create meaningful aliases for complex types
  • Document the purpose of type aliases
  • Examples:
    // Good: Clear type aliases
    using UserId = uint64_t;
    using ErrorCallback = std::function<void(const std::string&)>;
    using DataMap = std::unordered_map<std::string, std::shared_ptr<Data>>;
    
    // Avoid: Obscure typedef
    typedef unsigned long long int ull;
    

Enums

  • Prefer enum class over enum for type safety
  • Use explicit underlying types when needed
  • Prefix enum values with enum name for clarity (only if not using enum class)
  • Examples:
    // Best: enum class (scoped and type-safe)
    enum class Color : uint8_t
    {
        Red,
        Green,
        Blue
    };
    
    // Usage: Color::Red
    
    // Acceptable: Traditional enum with prefix
    enum MediaType
    {
        MEDIA_TYPE_AUDIO,
        MEDIA_TYPE_VIDEO,
        MEDIA_TYPE_SUBTITLE
    };
    

Error Handling

  • Use exceptions for exceptional conditions
  • Use std::optional for values that may not exist
  • Use std::expected (C++23) or similar for expected errors
  • Never throw from destructors
  • Document exceptions in function comments
  • Examples:
    // Good: Optional for nullable values
    std::optional<User> FindUser(const std::string& name);
    
    // Good: Exception for errors
    void LoadFile(const std::string& path)
    {
        if (path.empty())
        {
            throw std::invalid_argument("Path cannot be empty");
        }
        // ... load file
    }
    
    // Good: Error handling with optional
    auto user = FindUser("john");
    if (user.has_value())
    {
        ProcessUser(user.value());
    }
    

Memory Management

  • Prefer stack allocation over heap allocation when possible
  • Use smart pointers for heap-allocated objects
  • Use std::make_unique and std::make_shared for construction
  • Avoid naked new and delete
  • Use containers for collections of objects
  • Examples:
    // Good: Smart pointers
    auto data = std::make_unique<Data>();
    auto shared = std::make_shared<Config>();
    
    // Good: Stack allocation
    Data localData;
    std::array<int, 10> numbers;
    
    // Good: Containers
    std::vector<std::unique_ptr<Item>> items;
    

Comments and Documentation

  • Use // for all comments (single-line and multi-line)
  • Document public APIs with Doxygen-style comments in header files
  • Use traditional Doxygen syntax:
    • /// for Doxygen comments
    • \brief for brief descriptions
    • \param for parameters
    • \return for return values
  • Implementation files should use inline // comments for logic explanation
  • Comment the "why" not the "what"
  • Examples:
    /// \brief Sets the episode title
    /// \param title The new title for the episode
    void SetTitle(const std::string& title);
    
    // Implementation comment explaining reasoning
    // Use binary search because data is sorted
    auto it = std::lower_bound(data.begin(), data.end(), target);
    

Code Formatting

  • Use consistent indentation (4 spaces preferred)
  • Braces: Opening brace on next line for functions and blocks
  • Line length: Keep under 120 characters when practical
  • Use .clang-format configuration for automatic formatting
  • Example:
    // Function: opening brace on next line
    void MyClass::ProcessData(const std::vector<int>& data)
    {
        // Control structure: opening brace on next line
        if (nullptr == data_)
        {
            Initialize();
        }
    
        for (const auto& item : data)
        {
            ProcessItem(item);
        }
    }
    

Modern C++ Features

  • Use auto for type deduction when type is obvious from context
  • Use range-based for loops instead of iterators when possible
  • Use structured bindings (C++17) for multiple return values
  • Use std::string_view for non-owning string references
  • Use constexpr for compile-time constants
  • Examples:
    // Good: auto for obvious types
    auto config = std::make_unique<Config>();
    auto it = container.find(key);
    
    // Good: Range-based for
    for (const auto& item : items)
    {
        ProcessItem(item);
    }
    
    // Good: Structured bindings
    auto [success, value] = TryParse(input);
    
    // Good: string_view
    void ProcessName(std::string_view name);
    
    // Good: constexpr
    constexpr int MAX_SIZE = 1024;
    

Templates

  • Keep template code in headers (required by C++ standard)
  • Use concepts (C++20) to constrain template parameters
  • Provide clear error messages for template failures
  • Document template parameters and requirements
  • Examples:
    // C++20 concepts
    template<typename T>
    concept Drawable = requires(T obj)
    {
        obj.Draw();
    };
    
    template<Drawable T>
    void Render(const T& object)
    {
        object.Draw();
    }
    
    // Traditional template with static_assert
    template<typename T>
    class Container
    {
        static_assert(std::is_default_constructible_v<T>,
                      "T must be default constructible");
    };
    

Lambda Expressions

  • Use lambdas for short, local operations
  • Capture by reference [&] for local scope, by value [=] when needed
  • Be explicit with captures when clarity is important
  • Use mutable when lambda needs to modify captured values
  • Examples:
    // Good: Short algorithm
    std::sort(items.begin(), items.end(),
              [](const Item& a, const Item& b)
              {
                  return a.priority > b.priority;
              });
    
    // Good: Explicit captures
    int threshold = 10;
    auto filter = [threshold](int value)
    {
        return value > threshold;
    };
    
    // Good: Mutable lambda
    int counter = 0;
    auto increment = [counter]() mutable
    {
        return ++counter;
    };
    

Standard Library Usage

  • Prefer standard library over custom implementations
  • Use algorithms from <algorithm> header
  • Use standard containers (vector, map, set, etc.)
  • Use <string> for string handling
  • Use <filesystem> (C++17) for file operations
  • Examples:
    // Good: Standard algorithms
    std::sort(data.begin(), data.end());
    auto it = std::find_if(items.begin(), items.end(), predicate);
    
    // Good: Standard containers
    std::vector<int> numbers;
    std::unordered_map<std::string, Data> cache;
    
    // Good: Filesystem operations
    std::filesystem::path filePath = "/​path/​to/​file";
    if (std::filesystem::exists(filePath))
    {
        // Process file
    }
    

Const and Constexpr

  • Use const for runtime constants
  • Use constexpr for compile-time constants
  • Use consteval (C++20) to force compile-time evaluation
  • Mark functions constexpr when possible for compile-time optimization
  • Examples:
    // Runtime constant
    const int bufferSize = GetBufferSize();
    
    // Compile-time constant
    constexpr int MAX_USERS = 100;
    
    // Constexpr function
    constexpr int Square(int x)
    {
        return x * x;
    }
    
    // C++20 consteval (must be compile-time)
    consteval int Factorial(int n)
    {
        return (n <= 1) ? 1 : n * Factorial(n - 1);
    }
    

Platform Portability

  • Use standard C++ features when possible
  • Handle platform differences through preprocessor or runtime checks
  • Test on multiple platforms (Linux, macOS, Windows)
  • Use standard integer types from <cstdint>
  • Examples:
    #ifdef _WIN32
        // Windows-specific code
        #include <windows.h>
    #else
        // POSIX code
        #include <unistd.h>
    #endif
    
    // Use standard fixed-size types
    uint32_t value32;
    int64_t offset;
    

Compiler Warnings

  • Build with strict warnings enabled:
    • GCC/​Clang: -Wall -Wextra -Wpedantic
    • MSVC: /​W4
  • Treat warnings as errors in development builds
  • Fix all warnings - don't suppress them unless absolutely necessary
  • Document any warning suppressions with reasoning

Testing Strategy

  • Write unit tests for all public APIs
  • Test edge cases: null pointers, empty containers, boundary values
  • Use test frameworks (Google Test, Catch2, etc.)
  • Mock dependencies for isolated testing
  • Test on all target platforms
  • Examples:
    TEST(MyClassTest, ConstructorInitializesCorrectly)
    {
        MyClass obj;
        EXPECT_EQ(0, obj.GetValue());
    }
    
    TEST(MyClassTest, SetValueUpdatesCorrectly)
    {
        MyClass obj;
        obj.SetValue(42);
        EXPECT_EQ(42, obj.GetValue());
    }
    

Documentation

  • Document all public APIs in header files
  • Include purpose, parameters, return values, and exceptions
  • Use Doxygen format for API documentation
  • CRITICAL: Always verify documentation against actual implementation
  • README must show real API patterns, not fictional functions
  • Use actual class names and member names from header files
  • Integration examples must use real function signatures
  • Keep documentation synchronized with code changes
  • Example:
    /// \brief Creates a new user account
    /// \param username The unique username for the account
    /// \param email The user's email address
    /// \return A unique pointer to the created User object
    /// \throws std::invalid_argument if username is empty
    std::unique_ptr<User> CreateUser(
        const std::string& username,
        const std::string& email);
    

Documentation Tools

  • Use Doxygen for API documentation generation
  • Use Graphviz DOT for class diagrams and dependency diagrams
  • Use @dot...@enddot blocks for custom graphs
  • Keep diagrams clean and focused on domain relationships
  • Treat standard types (String, etc.) as primitives in diagrams

Code Review Checklist

  • All public APIs have Doxygen documentation
  • Const correctness applied throughout
  • Constant-left comparisons used consistently
  • Smart pointers used instead of raw pointers
  • RAII principles applied for resource management
  • Rule of Zero or Rule of Five followed correctly
  • No memory leaks (verified with valgrind or similar)
  • Code compiles without warnings on all platforms
  • Unit tests pass
  • Include guards or pragma once used correctly
  • Namespaces used appropriately
  • Modern C++ features used where beneficial
  • Code formatted according to project standards

Build System (CMake)

  • Use CMake 3.20+ for modern features
  • Support multiple platforms (Linux, macOS, Windows)
  • Support multiple compilers (GCC, Clang, MSVC)
  • Generate both shared and static libraries
  • Use CMake targets and properties
  • Example CMakeLists.txt structure:
    cmake_minimum_required(VERSION 3.20)
    project(MyProject VERSION 1.0.0 LANGUAGES CXX)
    
    set(CMAKE_CXX_STANDARD 23)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    # Library target
    add_library(mylib
        src/​MyClass.cpp
        src/​Helper.cpp
    )
    
    target_include_directories(mylib
        PUBLIC include
        PRIVATE src
    )
    
    # Executable target
    add_executable(myapp
        src/​main.cpp
    )
    
    target_link_libraries(myapp PRIVATE mylib)
    

More skills from heikopanjas

commit-changes logo
heikopanjas/agent-skills

commit-changes

Commit specific staged changes to a git repository using conventional commits format. Use when the user asks to commit, stage, or check in particular files or changes.

0 0
View
semantic-versioning logo
heikopanjas/agent-skills

semantic-versioning

Determine whether a version bump is required after code changes and apply semantic versioning (SemVer). Use when making code changes, fixing bugs, adding features, or introducing breaking changes to a project that uses SemVer.

0 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View