# C++ Coding Style Guide for OpenMB This document outlines the C++ coding standards for the OpenMB project. All contributors are expected to follow these guidelines to maintain consistency and code quality. ## Table of Contents - [Code Formatting](#code-formatting) - [Naming Conventions](#naming-conventions) - [Code Organization](#code-organization) - [Best Practices](#best-practices) - [Tooling](#tooling) ## Code Formatting ### Automated Formatting OpenMB uses **clang-format** to enforce consistent code formatting. The configuration is defined in `.clang-format` at the repository root. **Before submitting a merge request:** ```bash # Format your changed files clang-format -i path/to/your/file.cpp clang-format -i path/to/your/file.hpp ``` ### Key Formatting Rules #### Brace Style We use **Allman/BSD style** - braces on their own line for all constructs: ```cpp // Classes class MyClass { public: void myMethod(); }; // Functions void MyClass::myMethod() { // implementation } // Control statements if (condition) { doSomething(); } else { doSomethingElse(); } // Namespaces namespace MyNamespace { // content } ``` #### Indentation - **4 spaces** (no tabs) - All namespaces are indented - Access modifiers are outdented by 4 spaces from the class body ```cpp namespace MBWorld { class MyClass { public: // Outdented by 4 spaces void publicMethod(); private: int mMemberVariable; }; } ``` #### Line Length - Maximum **120 columns** - Break long lines logically, preferring to break before operators ```cpp // Good bool result = someVeryLongCondition && anotherCondition && yetAnotherCondition; // Bad - exceeds 120 columns bool result = someVeryLongCondition && anotherCondition && yetAnotherCondition && evenMoreConditions; ``` #### Pointer and Reference Alignment - **Left-aligned** with the type ```cpp // Good Ptr* ptr; const std::string& name; // Bad Ptr *ptr; const std::string &name; ``` #### Spacing - Space after control statement keywords: `if (`, `for (`, `while (` - No space after function names: `myFunction(args)` - Single space before trailing comments - No spaces inside parentheses, brackets, or angle brackets ```cpp // Good if (condition) { myFunction(arg1, arg2); std::vector numbers; // A comment } // Bad if( condition ){ myFunction (arg1,arg2); std::vector < int > numbers;//A comment } ``` ## Naming Conventions These conventions are enforced by **clang-tidy**. ### Classes and Structs **PascalCase** - capitalize the first letter of each word ```cpp class CreatureStats { }; class NpcStats { }; struct CellState { }; ``` ### Functions and Methods **camelCase** - lowercase first letter, capitalize subsequent words ```cpp void processData(); bool isValid() const; float getMaxSpeed(const Ptr& ptr) const; ``` ### Member Variables Prefix with `m` followed by **PascalCase** ```cpp class MyClass { private: int mHealthPoints; std::string mPlayerName; bool mIsActive; ESM::RefId mRaceId; }; ``` ### Local Variables and Parameters **camelBack** (same as functions) ```cpp void processPlayer(const Ptr& playerPtr, bool forceUpdate) { int currentHealth = getHealth(playerPtr); std::string characterName = getName(playerPtr); } ``` ### Namespaces **PascalCase** ```cpp namespace MBWorld { } namespace MBMechanics { } namespace ESM { } ``` **Exception:** External library namespaces may use their own conventions (e.g., `osg`, `osgDB`) ### Constants and Enums **PascalCase** for enum types, members follow context ```cpp enum class Specialization { Combat, Magic, Stealth }; // Enum class members use PascalCase enum RecordFlag { Persistent = 0x0400, Deleted = 0x0020 }; ``` ### Template Parameters **PascalCase** ```cpp template class Record : public RecordBase { T mData; }; ``` ## Code Organization ### Header Files #### Include Guards Use traditional `#ifndef` include guards: ```cpp #ifndef OPENMB_COMPONENTS_ESM_UTIL_H #define OPENMB_COMPONENTS_ESM_UTIL_H // Header content #endif ``` **Format:** `OPENMB__H` - Replace directory separators with underscores - Use uppercase - Path should be relative to project root (apps/ or components/) #### Include Order 1. Related header (for .cpp files) 2. C system headers 3. C++ standard library headers 4. External library headers 5. Project headers ```cpp #include "myclass.hpp" // Related header first #include #include #include #include #include #include #include #include "../mbbase/environment.hpp" ``` #### Header Structure ```cpp #ifndef OPENMB_APPS_OPENMB_MBWORLD_CLASS_H #define OPENMB_APPS_OPENMB_MBWORLD_CLASS_H // Includes // Forward declarations namespace ESM { class ESMReader; } namespace MBWorld { class Ptr; /// Brief class description class MyClass { public: MyClass(); ~MyClass(); /// Brief method description /// \param ptr Description of parameter /// \return Description of return value bool myMethod(const Ptr& ptr) const; private: int mData; }; } #endif ``` ### Source Files ```cpp #include "myclass.hpp" #include namespace MBWorld { MyClass::MyClass() : mData(0) { } bool MyClass::myMethod(const Ptr& ptr) const { // Implementation return true; } } ``` ## Best Practices ### Modern C++ (C++20) OpenMB targets **C++20**. Use modern C++ features appropriately: ```cpp // Use auto for complex types auto iterator = myMap.find(key); // Use smart pointers std::unique_ptr mDialog; // Use range-based for loops for (const auto& item : container) { process(item); } // Use nullptr instead of NULL or 0 Ptr* ptr = nullptr; // Use override keyword void myMethod() override; // Use constexpr where appropriate constexpr int MaxValue = 100; ``` ### Const Correctness Be diligent about const correctness: ```cpp class MyClass { public: // Const methods don't modify the object int getValue() const { return mValue; } // Use const references for parameters void setName(const std::string& name); // Use const pointers when appropriate void process(const MBWorld::ConstPtr& ptr) const; private: int mValue; }; ``` ### Virtual Functions - Always use `override` keyword for overridden virtual functions - Use `const` on virtual functions when they don't modify state - Add `= 0` for pure virtual functions ```cpp class Base { public: virtual ~Base() = default; virtual void myMethod() = 0; }; class Derived : public Base { public: void myMethod() override; // Use override, not virtual }; ``` ### Exception Handling Use exceptions for error conditions: ```cpp void MyClass::someMethod() { if (!isValid()) { throw std::runtime_error("Invalid state"); } } ``` ### Comments and Documentation Use Doxygen-style comments: ```cpp /// Brief description of the class class MyClass { public: /// Brief description of the method /// \param input Description of parameter /// \return Description of return value int processData(int input); int mPublicMember; ///< Brief description after member }; // Regular comments for implementation details // This algorithm uses a binary search because... ``` ### Memory Management - Prefer stack allocation over heap when possible - Use smart pointers (`std::unique_ptr`, `std::shared_ptr`) instead of raw `new`/`delete` - Follow RAII principles ```cpp // Good - automatic cleanup { std::unique_ptr dialog = std::make_unique(); dialog->show(); } // Automatically deleted // Bad - manual memory management Dialog* dialog = new Dialog(); dialog->show(); delete dialog; // Easy to forget or miss in error paths ``` ### Error Handling in Base Classes Use exceptions to indicate unsupported operations: ```cpp class Base { public: virtual ContainerStore& getContainerStore(const Ptr& ptr) const { throw std::runtime_error("class does not have a container store"); } }; ``` ### Type Aliases Use clear type aliases for complex types: ```cpp using ExtraList = std::vector; using OwnerMap = std::map; ``` ## Tooling ### clang-tidy OpenMB uses **clang-tidy** for static analysis. The configuration is in `.clang-tidy`. ```bash # Run clang-tidy on your files clang-tidy path/to/your/file.cpp -- -I/path/to/includes ``` **Key checks enabled:** - `portability-*` - Cross-platform compatibility - `clang-analyzer-*` - Static analysis - `modernize-avoid-bind` - Modern C++ practices - `readability-identifier-naming` - Naming conventions ### Running Checks Locally ```bash # Format code clang-format -i apps/openmb/myfile.cpp # Check formatting without modifying clang-format --dry-run --Werror apps/openmb/myfile.cpp # Run clang-tidy clang-tidy apps/openmb/myfile.cpp ``` ## Common Pitfalls ### Don't Mix Concerns - One class = one responsibility - One commit = one logical change - Don't combine formatting changes with functional changes ### Avoid Unnecessary Changes - Don't reformat code you didn't modify - Don't change whitespace in unrelated files - Focus changes on what's necessary for your feature/fix ### Platform Compatibility - OpenMB runs on Windows, Linux, and macOS - Avoid platform-specific code unless absolutely necessary - Use CMake for build configuration - Test on multiple platforms if possible ## Examples ### Good Class Example ```cpp #ifndef OPENMB_APPS_OPENMB_MBWORLD_MYCLASS_H #define OPENMB_APPS_OPENMB_MBWORLD_MYCLASS_H #include #include #include namespace MBWorld { class Ptr; /// Manages player inventory and equipment class InventoryManager { public: InventoryManager(); ~InventoryManager() = default; /// Add an item to the inventory /// \param itemId The ID of the item to add /// \param count Number of items to add /// \return True if successful bool addItem(const ESM::RefId& itemId, int count); /// Check if an item is in inventory /// \param itemId The ID to check /// \return True if item exists in inventory bool hasItem(const ESM::RefId& itemId) const; /// Get the total weight of all items float getTotalWeight() const; private: std::vector mItems; float mCurrentWeight; int mMaxCapacity; }; } #endif ``` ### Good Implementation Example ```cpp #include "inventorymanager.hpp" #include #include "../mbbase/environment.hpp" #include "../mbworld/esmstore.hpp" namespace MBWorld { InventoryManager::InventoryManager() : mCurrentWeight(0.0f) , mMaxCapacity(100) { } bool InventoryManager::addItem(const ESM::RefId& itemId, int count) { if (count <= 0) { return false; } const MBWorld::ESMStore& store = *MBBase::Environment::get().getESMStore(); // Add item logic here mItems.push_back(itemId); return true; } bool InventoryManager::hasItem(const ESM::RefId& itemId) const { return std::find(mItems.begin(), mItems.end(), itemId) != mItems.end(); } float InventoryManager::getTotalWeight() const { return mCurrentWeight; } } ``` ## Additional Resources - [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) - [Clang Format Documentation](https://clang.llvm.org/docs/ClangFormat.html) - [Clang Tidy Documentation](https://clang.llvm.org/extra/clang-tidy/) ## Questions? If you're unsure about any aspect of the coding style, look at existing code in the codebase for examples. Remember: Consistency is more important than personal preference. When in doubt, follow the existing patterns in the codebase.