C++ Style Guide¶
What's on this page
- Core content for this topic in the Inkorporated enterprise OS.
- Practical guidance, roles, or standards as applicable.
What this enables
- Consistent enterprise operations and decision-making.
- Shared language for humans and cyborg AI agents.
This document outlines the coding standards and best practices for C++ development within this project. Adhering to these guidelines ensures code consistency, readability, maintainability, and safety.
Table of Contents¶
- General Principles
- Code Layout
- Naming Conventions
- Files and Structure
- Documentation and Comments
- Classes and Structs
- Functions
- Modern C++ Features
- Error Handling
- Concurrency
- Templates and Generic Programming
- Casts and Types
- Memory Management
- Performance and Optimization
- Testing
- Recommended Tools
General Principles¶
- Standard: Target C++17 or C++20 (project dependent). Avoid non-standard extensions.
- C++ Core Guidelines: Follow the C++ Core Guidelines unless otherwise specified.
- Readability: Code is read much more often than it is written. Prioritize clarity over brevity.
- Consistency: Be consistent with the existing codebase.
- Safety: Prefer safe constructs (smart pointers,
std::array,std::vector) over unsafe ones (raw pointers, C-style arrays).
Code Layout¶
- Indentation: Use 4 spaces per indentation level. Do not use tabs.
- Line Length: Limit lines to 100 characters (or 80 if strict adherence is preferred).
- Braces:
- Use "K&R" or "1TBS" style (opening brace on the same line) for control structures and functions.
- Putting the opening brace on a new line (Allman style) is acceptable if consistent within a module.
- Whitespace:
- Spaces around binary operators (
+,-,=,==, etc.). - Space after comma in argument lists.
- No space between function name and opening parenthesis.
- Spaces around binary operators (
- Pointer/Reference Alignment: Align
*and&with the type, not the variable name (e.g.,int* ptrnotint *ptr).- Reasoning: The type is
int*(pointer to int).
- Reasoning: The type is
- Class Access Modifiers: Indent
public:,protected:, andprivate:labels.
class MyClass {
public:
void do_something();
private:
int m_value;
};
void do_something(int a, int b) {
if (a > b) {
return;
}
}
Naming Conventions¶
- Variables:
snake_case(e.g.,item_count,user_name). - Functions:
snake_case(e.g.,calculate_total,get_id). - Classes/Structs:
PascalCase(e.g.,UserManager,HttpRequest). - Constants:
kPascalCaseorUPPER_CASE_WITH_UNDERSCORES(e.g.,kMaxRetry,MAX_BUFFER_SIZE). - Private Members:
m_prefix +snake_caseor trailing underscore (e.g.,m_countorcount_). Be consistent. - Namespaces:
snake_case. - Template Types:
PascalCaseor single uppercase letterT(e.g.,T,InputIterator,ValueType). - Macros:
UPPER_CASE_WITH_UNDERSCORES(Avoid macros if possible).
Files and Structure¶
- Extensions:
.cppfor source files,.hppor.hfor headers. - Include Guards: Use
#pragma oncefor header files. It is widely supported and less error-prone than#ifndefguards. - Include Order:
- Related header (e.g.,
my_class.cppincludesmy_class.hpp). - C standard library headers (e.g.,
<cmath>). - C++ standard library headers (e.g.,
<vector>,<algorithm>). - Third-party libraries.
- Project headers.
- Separate groups with a blank line. Alphabetize within groups.
- Related header (e.g.,
- Forward Declarations: Use forward declarations where possible to reduce compile times and dependencies.
- Inline Functions: Define small functions (1-3 lines) inline in the header or class definition.
Documentation and Comments¶
- Style: Use Doxygen-compatible comments (
///or/** ... */). - Files: Top of file comment explaining the file's purpose.
- Classes/Functions: Document public APIs, including parameters, return values, exceptions, and thread safety.
- Implementation: Comment complex logic explaining why, not what.
/// @brief Calculates the Fibonacci sequence.
/// @param n The position in the sequence to calculate.
/// @return The nth Fibonacci number.
/// @throws std::invalid_argument if n is negative.
int fibonacci(int n);
Classes and Structs¶
- Structs: Use
structonly for passive data objects (PODs) with public data members. - Initialization: Use member initializer lists in constructors. Prefer in-class member initialization (C++11).
- Explicit: Mark single-argument constructors as
explicitto avoid implicit conversions. - Rule of 5: If you define a destructor, copy/move constructor, or copy/move assignment operator, define all 5 (or delete them).
- Zero Rule: Prefer classes that require no custom destructor/copy/move logic (rely on
std::string,std::vector, etc.). - Virtual: Use
overrideexplicitly for overridden virtual functions. Usefinalif further overriding is disallowed. - Destructors: Virtual destructors are required for base classes with virtual functions.
class Widget {
public:
explicit Widget(int id) : m_id(id) {} // Initializer list
virtual ~Widget() = default; // Virtual destructor
virtual void draw() const = 0;
private:
int m_id = 0; // In-class initialization
};
Functions¶
- Parameters:
- Primitive types: Pass by value (
int,bool,double). - Objects: Pass by
constreference (const std::string&) to avoid copies. - Mutable output: Prefer returning values or structs. If necessary, pass by pointer (
int* out_val). Avoid non-const references for output parameters as they are less obvious at the call site.
- Primitive types: Pass by value (
- Return Values: Rely on Return Value Optimization (RVO). Do not
std::movereturn values explicitly unless necessary. - Const Correctness: Mark member functions
constif they do not modify the object. Mark variablesconstwherever possible. - Noexcept: Mark functions
noexceptif they are guaranteed not to throw, especially move constructors and destructors.
Modern C++ Features¶
- Auto: Use
autowhen the type is obvious (e.g.,auto it = map.begin(),auto widget = std::make_unique<Widget>()) or redundant. Avoidautowhen it obscures the type. - Smart Pointers:
std::unique_ptr: Default choice for exclusive ownership.std::shared_ptr: Shared ownership. Usestd::make_shared.std::weak_ptr: Break cycles or temporary observation.- Avoid
newanddeletedirectly.
- Lambdas: Use lambdas for short, local callbacks. Capture explicitly (
[this],[&],[=]) rather than implicitly if scope is complex. - Range-based for: Use
for (const auto& item : items)for iteration. - Structured Binding: Use
auto [key, value] = *it;(C++17) for unpacking pairs/tuples.
Error Handling¶
- Exceptions: Use exceptions for error handling, not control flow.
- Standard Exceptions: Throw standard exceptions (
std::runtime_error,std::invalid_argument) or derive from them. - Noexcept: Ensure destructors, swap functions, and move operations do not throw.
- Alternatives: For expected failures (e.g., "file not found"), consider
std::optionalorstd::expected(C++23) over exceptions to indicate "no value".
Concurrency¶
- Standard Library: Use
std::thread,std::mutex,std::condition_variable. - Locks: Use
std::lock_guardorstd::unique_lockfor RAII-style locking. Never manuallylock()andunlock(). - Atomics: Use
std::atomicfor simple shared flags or counters. - Data Races: Ensure all shared data is protected. Use tools like ThreadSanitizer.
- Async: Prefer
std::asyncfor simple tasks returning a value (std::future).
Templates and Generic Programming¶
- Constraints: Use C++20 Concepts (
requires) to constrain templates for better error messages. - Type Traits: Use
<type_traits>(std::is_same_v,std::enable_if_t) for compile-time logic if Concepts are unavailable. - Implementation: Put template implementation in headers.
Casts and Types¶
- C++ Casts: Use
static_cast,dynamic_cast,reinterpret_cast,const_cast. - Avoid C-Style Casts: Never use
(int)x. They are dangerous and hard to grep. - Signed/Unsigned: Be careful comparing signed and unsigned integers. Prefer
intfor arithmetic unless bitwise operations or specific overflow behavior is needed. - Size: Use
std::size_tfor sizes and indices.
Memory Management¶
- RAII: Resource Acquisition Is Initialization. Wrap resources (memory, file handles, sockets) in classes that release them in the destructor.
- Containers: Prefer
std::vector,std::string,std::arrayover raw arrays. - String Views: Use
std::string_view(C++17) for read-only string arguments to avoid allocations.
Performance and Optimization¶
- Premature Optimization: Do not optimize without profiling. Readable code is often fast enough.
- Pass by Reference: Avoid copying large objects.
- Move Semantics: Implement move constructor/assignment for heavy objects.
- Constexpr: Use
constexprfor values and functions computed at compile time.
Testing¶
- Framework: Use Google Test (GTest) or Catch2.
- Unit Tests: Write unit tests for all public interfaces.
- Coverage: Aim for high test coverage.
- Structure: Keep tests in a separate directory (e.g.,
tests/) or alongside source (e.g.,_test.cpp).
Recommended Tools¶
- Formatter:
clang-format. Check in a.clang-formatfile. - Linter:
clang-tidyfor static analysis and modernizing code. - Build System: CMake is the industry standard.
- Compiler: Enable warnings:
-Wall -Wextra -Werror -pedantic. - Sanitizers: Use AddressSanitizer (ASan) and UndefinedBehaviorSanitizer (UBSan) during development/CI.