Instruction file imported from TheSethRose/DevRules (
.cursor/rules/languages/CPP20.mdc). Copyright stays with the author.
description: C++20/23 best practices and patterns for modern software development. globs: /*.c,/.cpp,**/.h,/*.hpp,/.cxx,CMakeLists.txt,.cmake,conanfile.txt,conanfile.py,vcpkg.json,meson.build,**/*.cc alwaysApply: false
C++20/23 Programming Guidelines
Core Principles & Philosophy
- RAII (Resource Acquisition Is Initialization): Manage resources (memory, files, locks, sockets) through objects whose destructors automatically release the resource.
- Value Semantics: Prefer passing and returning objects by value when copying is cheap and makes sense, promoting clarity and avoiding lifetime issues.
- Const Correctness: Use
constextensively for variables, pointers/references, and member functions that do not modify observable state. - Type Safety: Leverage the C++ type system. Avoid C-style casts; use C++ casts (
static_cast,dynamic_cast,const_cast,reinterpret_cast) appropriately and sparingly. - Standard Library First: Prefer using components from the C++ Standard Library (
std) over custom implementations or C-style approaches. - Zero-Overhead Principle: Don't pay for what you don't use. Use language features efficiently.
Modern C++ Features (C++11/14/17/20/23)
- Use
autofor type deduction where it improves readability, but use explicit types for clarity in interfaces or complex expressions. - Range-Based
forLoops: Use for iterating over containers (for (const auto& item : container)). - Smart Pointers: Use
std::unique_ptrfor unique ownership andstd::shared_ptrfor shared ownership. Avoid rawnewanddeletefor owning pointers. - Move Semantics: Understand and utilize move constructors and move assignment operators (often implicitly generated) for efficient resource transfer. Use
std::moveexplicitly when transferring ownership. - Lambdas: Use lambda expressions for inline functions, especially with standard library algorithms.
nullptr: Usenullptrinstead of0orNULLfor null pointers.enum class: Prefer strongly-typed, scoped enumerations (enum class) over unscopedenum.- Structured Bindings (C++17): Use for unpacking tuples, pairs, or struct/class members (
auto [key, value] = myMap.begin();). if constexpr(C++17): Use for compile-time conditional code based on template parameters.std::optional(C++17): Use for representing optional values, replacing nullable pointers or magic values.std::variant(C++17): Use for type-safe unions.std::string_view(C++17): Use for non-owning, read-only views of character sequences, avoiding unnecessary string copies.std::filesystem(C++17): Use for portable filesystem operations.- Concepts (C++20): Use to constrain template parameters, improving template error messages and expressing design intent.
- Ranges (C++20): Utilize the ranges library (
std::ranges) for more composable and expressive algorithms. - Coroutines (C++20): Use for cooperative multitasking and asynchronous operations (generators, async/await patterns).
- Modules (C++20): Consider using modules for better encapsulation and potentially faster compile times (compiler/build system support varies).
std::format(C++20): Prefer over iostreams or C-style formatting for type-safe and efficient string formatting.std::span(C++20): Use for non-owning views of contiguous sequences (like arrays, vectors).std::expected(C++23): Consider for functions that can return either a value or an error, as an alternative to exceptions orstd::optional+ error code.
Code Style & Organization
- Consistency: Maintain a consistent coding style (naming, formatting, brace style). Use tools like
clang-formatwith a defined style (e.g., in.clang-format). - Naming:
PascalCaseorcamelCasefor classes/structs (be consistent).camelCaseorsnake_casefor functions/methods/variables (be consistent).UPPER_SNAKE_CASEfor constants and enum members.snake_casefor namespaces and file names.
- Namespaces: Use namespaces to avoid naming collisions and organize code logically.
- Header/Source Separation: Separate declarations (
.h,.hpp) from definitions (.cpp,.cxx). Minimize includes in header files (use forward declarations where possible). - Includes: Use
#include <header>for standard/library headers and#include "path/to/header.h"for project headers.
Classes & Structs
- Rule of Zero/Three/Five: If a class manages resources directly (rare with RAII), define or delete the special member functions (copy/move constructor, copy/move assignment, destructor) correctly. Otherwise (Rule of Zero), rely on compiler-generated ones.
- Single Responsibility Principle: Design classes with a single, well-defined purpose.
- Interfaces: Define interfaces using abstract base classes with pure virtual functions or using Concepts (C++20).
- Prefer
structfor Plain Old Data (POD): Usestructwhen primarily aggregating data with few or no invariants. - Prefer
classfor Encapsulation/Invariants: Useclasswhen enforcing invariants or encapsulating behavior. Default toprivatemembers. - Mark Overrides: Use the
overridespecifier when overriding virtual functions. - Mark Finals: Use
finalon classes or virtual functions to prevent further derivation or overriding.
Error Handling
- Use Exceptions for Exceptional Errors: Throw exceptions for errors that prevent a function from meeting its preconditions, postconditions, or invariants (e.g., out of memory, file not found when required). Catch exceptions by
const&. - Use
std::expected(C++23) or Error Codes/std::optionalfor Expected Failures: For predictable error conditions (e.g., validation failure, resource unavailable but might become available), prefer non-exception mechanisms. - RAII for Cleanup: Ensure resources are cleaned up automatically, even when exceptions occur, by using RAII wrappers (smart pointers, standard containers, custom RAII classes).
Memory & Resource Management
- RAII is Key: See Core Principles.
- Smart Pointers: See Modern C++ Features.
- Standard Containers: Prefer
std::vector,std::string,std::array,std::map,std::unordered_map, etc., over manual memory management or C-style arrays/strings.
Build System & Dependencies
- Use a Standard Build System: CMake is the de facto standard. Meson is another modern option.
- Use a Package Manager: Manage external dependencies using Conan or vcpkg.
Testing
- Use a Testing Framework: Employ frameworks like Google Test, Catch2, or doctest.
- Unit Tests: Test individual classes and functions in isolation.
- Integration Tests: Test the interaction between components.
- Arrange-Act-Assert: Structure tests clearly.
- Test Doubles: Use mocks, fakes, or stubs where needed to isolate units.
C++ Programming Guidelines
Basic Principles
- Use English for all code and documentation.
- Always declare the type of each variable and function (parameters and return value).
- Create necessary types and classes.
- Use Doxygen style comments to document public classes and methods.
- Don't leave blank lines within a function.
- Follow the one-definition rule (ODR).
Nomenclature
- Use PascalCase for classes and structures.
- Use camelCase for variables, functions, and methods.
- Use ALL_CAPS for constants and macros.
- Use snake_case for file and directory names.
- Use UPPERCASE for environment variables.
- Avoid magic numbers and define constants.
- Start each function with a verb.
- Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc.
- Use complete words instead of abbreviations and ensure correct spelling.
- Except for standard abbreviations like API, URL, etc.
- Except for well-known abbreviations:
- i, j, k for loops
- err for errors
- ctx for contexts
- req, res for request/response parameters
Functions
- Write short functions with a single purpose. Less than 20 instructions.
- Name functions with a verb and something else.
- If it returns a boolean, use isX or hasX, canX, etc.
- If it doesn't return anything (void), use executeX or saveX, etc.
- Avoid nesting blocks by:
- Early checks and returns.
- Extraction to utility functions.
- Use standard library algorithms (std::for_each, std::transform, std::find, etc.) to avoid function nesting.
- Use lambda functions for simple operations.
- Use named functions for non-simple operations.
- Use default parameter values instead of checking for null or nullptr.
- Reduce function parameters using structs or classes
- Use an object to pass multiple parameters.
- Use an object to return multiple results.
- Declare necessary types for input arguments and output.
- Use a single level of abstraction.
Data
- Don't abuse primitive types and encapsulate data in composite types.
- Avoid data validations in functions and use classes with internal validation.
- Prefer immutability for data.
- Use const for data that doesn't change.
- Use constexpr for compile-time constants.
- Use std::optional for possibly null values.
Classes
- Follow SOLID principles.
- Prefer composition over inheritance.
- Declare interfaces as abstract classes or concepts.
- Write small classes with a single purpose.
- Less than 200 instructions.
- Less than 10 public methods.
- Less than 10 properties.
- Use the Rule of Five (or Rule of Zero) for resource management.
- Make member variables private and provide getters/setters where necessary.
- Use const-correctness for member functions.
Exceptions
- Use exceptions to handle errors you don't expect.
- If you catch an exception, it should be to:
- Fix an expected problem.
- Add context.
- Otherwise, use a global handler.
- Use std::optional, std::expected, or error codes for expected failures.
Memory Management
- Prefer smart pointers (std::unique_ptr, std::shared_ptr) over raw pointers.
- Use RAII (Resource Acquisition Is Initialization) principles.
- Avoid memory leaks by proper resource management.
- Use std::vector and other standard containers instead of C-style arrays.
Testing
- Follow the Arrange-Act-Assert convention for tests.
- Name test variables clearly.
- Follow the convention: inputX, mockX, actualX, expectedX, etc.
- Write unit tests for each public function.
- Use test doubles to simulate dependencies.
- Except for third-party dependencies that are not expensive to execute.
- Write integration tests for each module.
- Follow the Given-When-Then convention.
Project Structure
- Use modular architecture
- Organize code into logical directories:
- include/ for header files
- src/ for source files
- test/ for test files
- lib/ for libraries
- doc/ for documentation
- Use CMake or similar build system.
- Separate interface (.h) from implementation (.cpp).
- Use namespaces to organize code logically.
- Create a core namespace for foundational components.
- Create a utils namespace for utility functions.
Standard Library
- Use the C++ Standard Library whenever possible.
- Prefer std::string over C-style strings.
- Use std::vector, std::map, std::unordered_map, etc. for collections.
- Use std::optional, std::variant, std::any for modern type safety.
- Use std::filesystem for file operations.
- Use std::chrono for time-related operations.
Concurrency
- Use std::thread, std::mutex, std::lock_guard for thread safety.
- Prefer task-based parallelism over thread-based parallelism.
- Use std::atomic for atomic operations.
- Avoid data races by proper synchronization.
- Use thread-safe data structures when necessary.