This document outlines the coding standards and best practices for C++ development across all Bayat projects. Following these guidelines ensures code consistency, quality, and maintainability.
- C++ Version
- Code Style
- Naming Conventions
- Memory Management
- Project Structure
- Build System
- Headers and Includes
- Error Handling
- Performance Considerations
- Concurrency
- Security Best Practices
- Testing
- Documentation
- Tools and IDE Configuration
- Use C++17 for all new projects
- C++14 is the minimum supported version for existing projects
- Document C++ version requirements in project README and build files
- Use C++20 features only when they provide significant benefits and are supported by all target compilers
All C++ code should follow a consistent style based on the Google C++ Style Guide with the following adaptations:
- Use 2 spaces for indentation (no tabs)
- Maximum line length of 100 characters
- Use clang-format for automated code formatting
- Avoid excessive horizontal spacing (no multiple spaces for alignment)
- Place opening braces on the same line as control statements
- Place opening braces for function/method definitions on a new line
- Use
nullptr
instead ofNULL
or0
- Use
auto
when it improves readability, but don't overuse it
// Good C++ style example
#include <string>
#include <vector>
namespace bayat {
namespace utils {
class StringProcessor {
public:
StringProcessor();
~StringProcessor();
// Process a string and return a modified version
std::string Process(const std::string& input);
private:
bool IsValid(const std::string& text) const;
std::vector<std::string> cached_results_;
};
StringProcessor::StringProcessor()
{
// Constructor implementation
}
std::string StringProcessor::Process(const std::string& input)
{
if (!IsValid(input)) {
return "";
}
std::string result = input;
// Process the string...
for (auto& c : result) {
if (c == ' ') {
c = '_';
}
}
cached_results_.push_back(result);
return result;
}
} // namespace utils
} // namespace bayat
Use the following .clang-format
file as a baseline:
---
Language: Cpp
BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
FixNamespaceComments: true
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 2
UseTab: Never
- Header files:
.h
extension - Source files:
.cpp
extension - Use all lowercase with underscores for filenames
- Match source file name to the main class/functionality it implements
- Example:
string_processor.h
andstring_processor.cpp
- Use lowercase with underscores
- Group related files into directories based on functionality
- Example:
core/
,rendering/
,physics/
- Use CamelCase for class, struct, enum, and typedef names
- Example:
class StringProcessor
,struct UserConfig
- Use snake_case for variable names
- Class member variables should have a trailing underscore
- Example:
std::string user_name;
,int frame_count_;
- Use descriptive names that indicate purpose
- Boolean variables should be named as predicates (e.g.,
is_valid
,has_data
)
- Use kCamelCase for constants (with k prefix)
- Example:
const int kMaxBufferSize = 1024;
- Use CamelCase for functions and methods
- Accessor methods should match the property name
- Example:
void ProcessData()
,int count() const
- Use lowercase for namespace names
- Use company or project name as the top-level namespace
- Example:
namespace bayat { namespace rendering { ... } }
-
Enum names should use CamelCase
-
Enum values should use kCamelCase (with k prefix)
-
Example:
enum class LogLevel { kDebug, kInfo, kWarning, kError, kFatal };
- Prefer automatic variables and RAII (Resource Acquisition Is Initialization)
- Avoid raw pointers when ownership semantics are required
- Use smart pointers to express ownership
- Explicitly delete copy/move constructors and assignment operators when not needed
- Be consistent with memory allocation (e.g., don't mix
new
/delete
withmalloc
/free
)
- Use
std::unique_ptr
for exclusive ownership - Use
std::shared_ptr
for shared ownership - Use
std::weak_ptr
to break reference cycles withstd::shared_ptr
- Prefer using
std::make_unique
andstd::make_shared
over direct constructor calls
Example:
// Prefer this:
auto user = std::make_unique<User>("John", 30);
// Over:
std::unique_ptr<User> user(new User("John", 30));
- Use RAII to manage resources (files, memory, mutexes, etc.)
- Create wrapper classes that handle resource cleanup in destructors
- Avoid naked
new
anddelete
operations - Use containers and smart pointers to manage collections and object lifetimes
Example:
class FileHandler {
public:
explicit FileHandler(const std::string& filename) {
file_ = std::fopen(filename.c_str(), "r");
if (!file_) {
throw std::runtime_error("Failed to open file");
}
}
~FileHandler() {
if (file_) {
std::fclose(file_);
}
}
// Prevent copying
FileHandler(const FileHandler&) = delete;
FileHandler& operator=(const FileHandler&) = delete;
// Allow moving
FileHandler(FileHandler&& other) noexcept : file_(other.file_) {
other.file_ = nullptr;
}
FileHandler& operator=(FileHandler&& other) noexcept {
if (this != &other) {
if (file_) {
std::fclose(file_);
}
file_ = other.file_;
other.file_ = nullptr;
}
return *this;
}
// File operations...
std::string ReadLine();
private:
FILE* file_;
};
Organize C++ projects with the following directory structure:
project_name/
├── CMakeLists.txt # Main CMake configuration
├── README.md # Project documentation
├── LICENSE # License information
├── .gitignore # Git ignore file
├── .clang-format # Clang-format configuration
├── src/ # Source code
│ ├── CMakeLists.txt # Source build configuration
│ ├── main.cpp # Application entry point
│ ├── core/ # Core functionality
│ │ ├── core.h # Core public interface
│ │ ├── subsystem1.h # Subsystem 1 header
│ │ └── subsystem1.cpp # Subsystem 1 implementation
│ └── module2/ # Another module
│ ├── module2.h # Module 2 public interface
│ └── module2.cpp # Module 2 implementation
├── include/ # Public headers
│ └── project_name/ # Project-specific headers
│ ├── api.h # Main API header
│ └── types.h # Common types
├── tests/ # Test directory
│ ├── CMakeLists.txt # Test build configuration
│ ├── core/ # Core module tests
│ │ └── subsystem1_test.cpp
│ └── module2/ # Module 2 tests
│ └── module2_test.cpp
├── examples/ # Example applications
│ ├── CMakeLists.txt # Examples build configuration
│ └── example1.cpp # Example 1
├── docs/ # Documentation
│ └── api.md # API documentation
└── third_party/ # Third-party dependencies
└── some_library/ # Third-party library
- Use CMake as the primary build system
- Set minimum required CMake version to 3.14 or higher
- Use target-based approach with modern CMake
- Define targets with clear properties and dependencies
- Use separate CMakeLists.txt files for different directories
- Prefer to use find_package for dependencies when available
Example CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(ProjectName VERSION 1.0.0 LANGUAGES CXX)
# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Build options
option(BUILD_TESTS "Build tests" ON)
option(BUILD_EXAMPLES "Build examples" ON)
# Add third-party dependencies
find_package(Boost REQUIRED COMPONENTS system filesystem)
# Define library target
add_library(${PROJECT_NAME}
src/core/subsystem1.cpp
src/module2/module2.cpp
)
# Set include directories
target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
# Link dependencies
target_link_libraries(${PROJECT_NAME}
PUBLIC
Boost::system
Boost::filesystem
)
# Compiler flags
target_compile_options(${PROJECT_NAME}
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic -Werror>
)
# Build tests if enabled
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# Build examples if enabled
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
# Installation rules
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
install(DIRECTORY include/
DESTINATION include
)
- Enable high warning levels (
-Wall -Wextra -Wpedantic
or/W4
) - Treat warnings as errors in CI builds (
-Werror
or/WX
) - Enable debug information in debug builds
- Enable optimizations in release builds
- Consider enabling sanitizers in debug/test builds
- Use
#pragma once
for all header files - Alternatively, use traditional include guards with project-specific prefix
Example:
// Using #pragma once
#pragma once
// Or using traditional include guards
#ifndef BAYAT_MODULE_HEADER_H_
#define BAYAT_MODULE_HEADER_H_
// Header contents...
#endif // BAYAT_MODULE_HEADER_H_
- Group and order includes as follows:
- Related header (e.g.,
foo.cpp
includesfoo.h
first) - C system headers (e.g.,
<cstdlib>
) - C++ standard library headers (e.g.,
<iostream>
) - Other libraries' headers
- Your project's headers
- Related header (e.g.,
- Within each group, order alphabetically
- Use angle brackets for system and external headers (
<header>
) - Use quotes for project headers (
"header.h"
)
Example:
// In foo.cpp
#include "foo.h" // Related header
#include <cstdlib> // C system header
#include <cstring>
#include <iostream> // C++ standard library header
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp> // Other libraries
#include "base/common.h" // Project headers
#include "foo/bar.h"
- Use forward declarations when possible to reduce compilation dependencies
- Include the full header when using derived classes, template details, or inlined functions
Example:
// Forward declarations
namespace bayat {
class User; // Forward declaration
} // namespace bayat
// Later in the code
void ProcessUser(const bayat::User* user);
- Use exceptions for exceptional conditions that cannot be handled locally
- Document exception safety guarantees for functions and methods
- Catch exceptions by reference
- Catch specific exceptions before more general ones
- Provide meaningful error messages in exceptions
- Consider creating a hierarchy of custom exception classes for your project
Example:
class DatabaseError : public std::runtime_error {
public:
explicit DatabaseError(const std::string& message)
: std::runtime_error("Database error: " + message) {}
};
class ConnectionError : public DatabaseError {
public:
explicit ConnectionError(const std::string& message)
: DatabaseError("Connection failed: " + message) {}
};
void ConnectToDatabase() {
try {
// Connection logic...
if (!connection_established) {
throw ConnectionError("Could not connect to database server");
}
} catch (const ConnectionError& e) {
// Handle specific error
LogError(e.what());
throw; // Rethrow for caller to handle
} catch (const DatabaseError& e) {
// Handle more general database error
LogError(e.what());
// Attempt recovery...
} catch (const std::exception& e) {
// Handle standard library exceptions
LogFatalError(e.what());
throw;
}
}
- Use error codes when exceptions are not appropriate (e.g., performance-critical code)
- Use
std::error_code
or custom error code types - Provide clear documentation for error code meanings
- Consider using Boost.System or similar libraries for error code management
- Use
assert()
to check for conditions that should never happen - Use runtime checks for conditions that might fail due to external factors
- Consider using a more feature-rich assertion library for production code
- Write clean, readable code first, then optimize if necessary
- Measure performance before and after optimizations
- Focus on algorithmic improvements before micro-optimizations
- Use appropriate data structures for the task
- Minimize memory allocations in performance-critical paths
- Avoid copies of large objects
- Use move semantics where appropriate
- Use references for function parameters to avoid copying
- Consider using
constexpr
for compile-time evaluation - Use reserve() for vectors and other containers when size is known
- Prefer pre-increment (++i) over post-increment (i++) for iterators
- Use range-based for loops when possible
Example:
// Less efficient
void ProcessNames(std::vector<std::string> names) { // Copies the entire vector
for (int i = 0; i < names.size(); i++) { // Less idiomatic
// Process each name
}
}
// More efficient
void ProcessNames(const std::vector<std::string>& names) { // Reference, no copy
names.reserve(names.size() + 10); // Reserve space if adding elements
for (const auto& name : names) { // Range-based for loop, no copy of string
// Process each name
}
}
- Use
std::thread
for basic threading needs - Use
std::async
andstd::future
for task-based parallelism - Document thread safety for classes and functions
- Be explicit about which resources are shared between threads
- Use thread-local storage when appropriate
- Use
std::mutex
andstd::lock_guard
for basic synchronization - Use
std::unique_lock
when more flexibility is needed - Use
std::shared_mutex
for reader-writer locks - Prefer higher-level synchronization mechanisms over raw locks
- Use
std::atomic
for simple shared variables - Always lock in the same order to avoid deadlocks
Example:
class ThreadSafeCounter {
public:
ThreadSafeCounter() = default;
// Multiple threads can read the counter's value.
unsigned int Get() const {
std::shared_lock<std::shared_mutex> lock(mutex_);
return value_;
}
// Only one thread can increment the counter at a time.
void Increment() {
std::unique_lock<std::shared_mutex> lock(mutex_);
++value_;
}
// Only one thread can reset the counter at a time.
void Reset() {
std::unique_lock<std::shared_mutex> lock(mutex_);
value_ = 0;
}
private:
mutable std::shared_mutex mutex_;
unsigned int value_ = 0;
};
- Use
std::future
andstd::promise
for asynchronous operations - Consider using a task-based concurrency library for complex scenarios
- Document the execution context (which thread) for callbacks and handlers
- Be careful with capturing variables in lambdas for async operations
Example:
std::future<int> ComputeValueAsync() {
return std::async(std::launch::async, []() {
// Perform expensive computation
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42;
});
}
void UseAsyncValue() {
auto future = ComputeValueAsync();
// Do other work while computation is running
// Wait for result when needed
try {
int result = future.get(); // Will block until result is available
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "Computation failed: " << e.what() << std::endl;
}
}
- Validate all inputs, especially those from external sources
- Use secure string handling to prevent buffer overflows
- Validate array indices before use
- Check integer operations for overflow and underflow
- Avoid raw memory operations when possible
- Use bounds-checking containers and iterators
- Initialize all variables before use
- Use memory sanitizers in testing
- Use constant-time comparison for sensitive data like passwords
- Sanitize sensitive data in memory when no longer needed
- Don't log sensitive information
- Use secure random number generators for cryptographic purposes
Example:
// Insecure - vulnerable to timing attacks
bool VerifyPasswordInsecure(const std::string& stored_hash, const std::string& input) {
return stored_hash == ComputeHash(input); // Variable-time comparison
}
// Secure - constant-time comparison
bool VerifyPasswordSecure(const std::string& stored_hash, const std::string& input) {
const std::string computed_hash = ComputeHash(input);
if (stored_hash.length() != computed_hash.length()) {
return false;
}
// Constant-time comparison
int result = 0;
for (size_t i = 0; i < stored_hash.length(); ++i) {
result |= stored_hash[i] ^ computed_hash[i];
}
return result == 0;
}
- Use Google Test, Catch2, or Boost.Test for unit testing
- Test each class and function in isolation
- Mock external dependencies using Google Mock or similar
- Organize tests to mirror the structure of the source code
- Name test files with a
_test
suffix (e.g.,string_processor_test.cpp
)
Example (using Google Test):
#include <gtest/gtest.h>
#include "string_processor.h"
namespace bayat {
namespace utils {
namespace {
TEST(StringProcessorTest, ProcessReplaceSpacesWithUnderscores) {
StringProcessor processor;
std::string input = "hello world";
std::string expected = "hello_world";
EXPECT_EQ(processor.Process(input), expected);
}
TEST(StringProcessorTest, ProcessEmptyString) {
StringProcessor processor;
std::string input = "";
EXPECT_EQ(processor.Process(input), "");
}
} // namespace
} // namespace utils
} // namespace bayat
- Test interactions between components
- Test with realistic data and environments
- Test both success and failure paths
- Use fixtures for complex test setup
- Aim for at least 80% code coverage
- Focus on testing complex logic and edge cases
- Use coverage tools like gcov/lcov or OpenCppCoverage
- Include coverage reports in CI pipelines
- Use Doxygen-style comments for classes, functions, and methods
- Document parameters, return values, exceptions, and side effects
- Include usage examples for complex interfaces
- Keep documentation close to the code (in header files)
Example:
/**
* @brief Processes a string according to defined rules.
*
* This function takes an input string and processes it by replacing
* spaces with underscores and performing other transformations.
*
* @param input The string to process
* @return The processed string
* @throws std::invalid_argument If the input string is invalid
*
* @note This function is not thread-safe
*
* @code
* StringProcessor processor;
* std::string result = processor.Process("Hello World");
* // result is now "Hello_World"
* @endcode
*/
std::string Process(const std::string& input);
- Provide comprehensive documentation for public APIs
- Include getting started guides and tutorials
- Document versioning and compatibility guarantees
- Use clear examples for common use cases
- Document performance characteristics and thread safety
- Maintain a README.md with project overview
- Include build instructions
- Document dependencies and system requirements
- Provide contribution guidelines
- Include license information
- Compiler: GCC, Clang, or MSVC
- Build System: CMake
- Package Manager: Conan or vcpkg
- Code Formatter: clang-format
- Static Analyzer: clang-tidy, Cppcheck
- Dynamic Analysis: Valgrind, AddressSanitizer
- Documentation: Doxygen
- Unit Testing: Google Test, Catch2
- CI/CD: GitHub Actions, GitLab CI, Jenkins
Recommended extensions:
- C/C++ (Microsoft)
- CMake Tools
- CMake
- CodeLLDB
- clangd
- C++ TestMate
Settings (settings.json):
{
"C_Cpp.clang_format_fallbackStyle": "Google",
"C_Cpp.clang_format_style": "file",
"C_Cpp.default.cppStandard": "c++17",
"C_Cpp.formatting": "clangFormat",
"editor.formatOnSave": true,
"cmake.configureOnOpen": true,
"cmake.buildDirectory": "${workspaceFolder}/build"
}
- Enable C++17 features
- Use clang-format for formatting
- Enable static analysis
- Configure /W4 warning level
- Enable clang-format
- Configure CMake integration
- Enable Clang-Tidy integration
- Use Google style as base formatting style
Version | Date | Description |
---|---|---|
1.0 | 2025-03-20 | Initial version |