Skip to content

Instantly share code, notes, and snippets.

@paigeadelethompson
Last active September 22, 2025 08:51
Show Gist options
  • Select an option

  • Save paigeadelethompson/7a28104b1c91913d74c32897a1534754 to your computer and use it in GitHub Desktop.

Select an option

Save paigeadelethompson/7a28104b1c91913d74c32897a1534754 to your computer and use it in GitHub Desktop.

C++ Development Conventions

This document outlines the coding and project structure conventions for C++ projects.

C++ Language Standards

  • C++23 is always required
  • Use modern C++ features and idioms

Compiler and Build Configuration

Compiler Preferences

  • Prefer clang++ over g++ when available
  • Use Werror - treat all warnings as errors
  • Enable compiler hardening options for security and robustness

CMake Compiler Configuration

# Prefer clang++ if available
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    message(STATUS "Using Clang compiler")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    message(STATUS "Using GCC compiler")
endif()

# Enable warnings as errors
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")

# Compiler hardening options
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2 -Wformat-security")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wstack-protector")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fPIE")

# Linker hardening options (separate from compiler flags)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")

Hardening Options Explained

  • -Werror - Treat all warnings as errors (fail build on warnings)
  • -Wall -Wextra -Wpedantic - Enable comprehensive warning sets
  • -Wformat=2 -Wformat-security - Warn about format string vulnerabilities
  • -Wstack-protector - Enable stack protection warnings (compatible with both Clang and GCC)
  • -fstack-protector - Enable stack protection (compatible with both Clang and GCC)
  • -D_FORTIFY_SOURCE=2 - Enable buffer overflow detection
  • -fPIC -fPIE - Position independent code/executables
  • -Wl,-z,relro -Wl,-z,now - Linker hardening (relocation read-only, immediate binding)
    • Note: These linker options work with both clang++ and g++ linkers
    • relro: Makes relocation sections read-only after loading
    • now: Forces immediate binding of all symbols at load time

Namespace Conventions

Use compact namespace syntax that follows the directory structure:

CRITICAL RULE: The root namespace MUST match the project directory name exactly.

For single project directories:

// ✅ Correct - root namespace matches project directory name
namespace projectname::component {
    // code here
}

namespace projectname::module {
    // code here
}

For multiple project directories:

// ✅ Correct - namespace includes workspace and project directory name
namespace workspace::projectname1::component {
    // code here
}

namespace workspace::projectname1::module {
    // code here
}

namespace workspace::projectname2::handler {
    // code here
}

namespace workspace::projectname2::database {
    // code here
}

❌ Incorrect patterns:

// ❌ Incorrect - generic root namespace
namespace app::component {
    // code here
}

// ❌ Incorrect - don't include src/ or include/ in namespace
namespace projectname::src::component {
    // code here
}

namespace projectname::include::module {
    // code here
}

// ❌ Incorrect - don't use nested namespace syntax
namespace projectname {
namespace component {
    // code here
}
}

Project Structure

Main Directories

  • src/ - Source files (.cpp)
  • include/ - Header files (.hpp)

CMake Include Path Options

Choose one of the following approaches:

Option 1: Source/include as include path

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
#include <subdir/header.hpp>

Commenting Standards

Comment Standards

  • Avoid // comments - only use when necessary for non-self-explanatory code
  • Prefer self-documenting code over comments

Documentation Comments

  • Documentation comments in headers only
  • Use standard documentation format in header files
  • Implementation files should not contain documentation comments
  • Document all public functions and properties in header files
  • Document class purpose and usage in header files

Function Documentation

Document all public functions in header files with:

/**
 * @brief Brief description of what the function does
 * @param paramName Description of parameter
 * @return Description of return value
 * @throws ExceptionType When this exception is thrown
 */
void myFunction(int paramName);

Property Documentation

Document all public properties in header files with:

/**
 * @brief Brief description of what the property represents
 * @details Additional details if needed
 */
std::string myProperty;

Class Documentation

Document classes with purpose and usage:

/**
 * @brief Brief description of class purpose
 * @details Detailed description of class functionality and usage
 */
class MyClass {
    // class implementation
};

File Headers

Every file must have a header comment block with:

/**
 * @file filename.cpp
 * @brief Brief description of file purpose
 * @details Detailed description if needed
 * 
 * @author paigeadelethompson
 * @year [Use: date +%Y to get current year]
 */

Subdirectory Organization

Single Class Files

When you have a single class:

  • Use single-word filename: parser.hpp, validator.hpp
  • No subdirectory needed
  • Example: src/parser.cpp with include/parser.hpp

Multiple Related Classes

When you have multiple related classes:

  • Create subdirectory: src/component/, include/component/
  • Each class gets its own file: controller.hpp, service.hpp, manager.hpp
  • Must have at least lib.hpp and lib.cpp (even if empty)
  • Namespace follows pattern: namespace PROJECTNAME::subdir
  • Example: src/component/ with include/component/controller.hpp, include/component/service.hpp
  • Each class uses namespace projectname::component

File Organization Rules

Primary Rule: Use single-word filenames for all classes. Create subdirectories when you have multiple related classes OR when the class name is a compound word.

CRITICAL RULE: Compound words MUST be split into subdirectories, even for single classes.

When to use subdirectories:

  • Multiple classes that would have the same single-word filename
  • Related classes that need to be grouped together
  • Compound word class names (even for single classes)
  • Example: component/controller.hpp, component/service.hpp, component/manager.hpp (all component-related)
  • Example: window/main.hpp (compound word "mainwindow" → "window/main")

When NOT to use subdirectories:

  • Single classes with unique single-word names
  • Example: parser.hpp, validator.hpp, processor.hpp (each is unique)

Subdirectory Requirements:

  • Each subdirectory must have lib.hpp and lib.cpp (even if empty)
  • Namespace follows pattern: namespace PROJECTNAME::subdir
  • Example: namespace projectname::component for component/ files
  • Example: namespace projectname::window for window/ files

File naming examples:

  • parser.hpp (single class, single word, no subdirectory needed)
  • validator.hpp (single class, single word, no subdirectory needed)
  • component/controller.hpp (multiple component classes, subdirectory needed)
  • window/main.hpp (compound word "mainwindow" → subdirectory required)
  • textparser.hpp (compound word, use parser.hpp OR text/parser.hpp)
  • mainwindow.hpp (compound word, use window/main.hpp)
  • mainwindow.cpp (compound word, use window/main.cpp)

CMake Structure

Directory Organization

  • cmake/ directory is for CMake plugins and modules only
  • Each subdirectory should have its own CMakeLists.txt

Top-Level CMakeLists.txt Responsibilities

  • Define ALL include paths (not done in sub-makefiles)
    • Define include path as a variable: set(GLOBAL_PROJ_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
    • Use include_directories(${GLOBAL_PROJ_INCLUDE_DIR}) to set global include path
    • NEVER define specific header file paths - only directory paths
    • This allows #include <subdir/header.hpp> syntax throughout the project
  • Handle linking (not done in sub-makefiles)
  • All linking must be static - no dynamic linking
  • Include subdirectory makefiles

Subdirectory CMakeLists.txt Responsibilities

  • Only produce .a artifacts (static libraries)
  • All linking must be static - no dynamic linking
  • ONLY reference the global include directory: include_directories(${GLOBAL_PROJ_INCLUDE_DIR})
  • NEVER define specific header file paths or absolute paths
  • Link their own subdirectories - each subdir is responsible for linking its subdirs
  • No linking to other peer directories (handled by parent)
  • Focus on source compilation and subdirectory linking only

CMake Target Naming Conventions

  • CMake targets should follow namespace structure
  • Target names match directory structure where possible
  • Use descriptive target names that reflect the component purpose

Target naming examples:

Single project directory:

# Targets follow namespace: projectname::component
add_library(projectname_component STATIC controller.cpp service.cpp)
add_library(projectname_window STATIC main.cpp)
add_library(projectname_module STATIC processor.cpp handler.cpp)

Multiple project directories:

# Targets follow namespace: workspace::projectname::component
add_library(workspace_projectname1_component STATIC controller.cpp service.cpp)
add_library(workspace_projectname1_window STATIC main.cpp)
add_library(workspace_projectname2_handler STATIC request.cpp)
add_library(workspace_projectname2_database STATIC connection.cpp)

Target naming rules:

  • Single project: Use projectname_component, projectname_window, projectname_module
  • Multiple projects: Use workspace_projectname1_component, workspace_projectname2_handler, etc.
  • Always prefix with project name for clarity and consistency
  • Use underscores to separate all naming components
  • Workspace prefix: Include workspace name when multiple projects exist

Example subdirectory CMakeLists.txt:

# Include the same directories as the main project
include_directories(${GLOBAL_PROJ_INCLUDE_DIR})

# Add subdirectories (each subdir links its own subdirs)
add_subdirectory(service)

# Only compile sources to static library
add_library(projectname_component STATIC
    source1.cpp
    source2.cpp
)

# Link subdirectory libraries
target_link_libraries(projectname_component projectname_service)

Code Style

clang-format Configuration

Project .clang-format File

Every C++ project should include a .clang-format configuration file in the project root:

# .clang-format
NamespaceIndentation: All
UseTab: Never

Formatting Commands

# Format all C++ files in the project
find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format -i

# Preview changes without modifying files
find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format

# Format specific directories
find src -name "*.cpp" | xargs clang-format -i
find include -name "*.hpp" | xargs clang-format -i

Build System Integration

qmake Integration: Add to your .pro file:

# clang-format target
clang-format.target = clang-format
clang-format.commands = test -f .clang-format && find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format -i
clang-format.depends = FORCE
QMAKE_EXTRA_TARGETS += clang-format

# clang-format-check target (preview only)
clang-format-check.target = clang-format-check
clang-format-check.commands = test -f .clang-format && find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format
clang-format-check.depends = FORCE
QMAKE_EXTRA_TARGETS += clang-format-check

CMake Integration: Add to your CMakeLists.txt:

# Check if .clang-format exists
if(EXISTS "${CMAKE_SOURCE_DIR}/.clang-format")
    # Find clang-format
    find_program(CLANG_FORMAT clang-format)
    
    if(CLANG_FORMAT)
        # clang-format target
        add_custom_target(clang-format
            COMMAND ${CLANG_FORMAT} -i $<TARGET_PROPERTY:SOURCES>
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
            COMMENT "Formatting code with clang-format"
        )
        
        # clang-format-check target (preview only)
        add_custom_target(clang-format-check
            COMMAND ${CLANG_FORMAT} $<TARGET_PROPERTY:SOURCES>
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
            COMMENT "Checking code formatting with clang-format"
        )
    else()
        message(WARNING "clang-format not found - formatting targets not available")
    endif()
else()
    message(WARNING ".clang-format file not found - formatting targets not available")
endif()

Usage:

# qmake projects
make clang-format        # Format all files
make clang-format-check  # Preview changes

# CMake projects  
make clang-format        # Format all files
make clang-format-check  # Preview changes

.clang-format Benefits

  • Consistent code formatting across the entire project
  • Automatic namespace indentation with NamespaceIndentation: All
  • No tabs with UseTab: Never (spaces only)
  • Team consistency - all developers use the same formatting rules
  • IDE integration - most editors can use the .clang-format file automatically

File Organization

  • One class per file - each class gets its own header and implementation file
  • Single word filenames - no underscores, dashes, spaces, or compound words
    • Directory names can be compound: component/, window/, module/
    • File names must be single words: controller.hpp, service.hpp, manager.hpp
    • component/controller.hpp, window/main.hpp, module/processor.hpp
    • main_controller.hpp, component-service.hpp, serviceManager.hpp, main controller.hpp, textparser.hpp, service manager.hpp

Headers

  • Use #include <header.hpp> syntax (angle brackets)
  • No forward declarations when full includes are available
  • Include all necessary headers directly
  • All header files must have include guards
  • Source files must use .cpp extension
  • Header files must use .hpp extension

Implementation

  • Use meaningful variable and function names
  • Follow modern C++ conventions and patterns
  • Handle unused parameters appropriately

Naming Conventions

  • Class and Struct Names: MyClassName (PascalCase)
  • Function Names: myFuncName (camelCase)
  • Constants: ALL_CAPS (UPPER_SNAKE_CASE)
  • Enums: ALL_CAPS (UPPER_SNAKE_CASE)
  • Variables: myVariableName (camelCase)

Example File Structure

Single Project Directory

projectname/                # Root namespace = "projectname"
├── src/
│   ├── main.cpp
│   ├── parser.cpp          # Single class, no subdirectory
│   ├── validator.cpp       # Single class, no subdirectory
│   ├── window/             # Compound word "mainwindow" → subdirectory
│   │   ├── CMakeLists.txt
│   │   ├── lib.cpp
│   │   └── main.cpp        # MainWindow class
│   ├── component/
│   │   ├── CMakeLists.txt
│   │   ├── lib.cpp
│   │   ├── controller.cpp
│   │   ├── service.cpp
│   │   └── manager/
│   │       ├── CMakeLists.txt
│   │       ├── lib.cpp
│   │       └── cache.cpp
│   └── module/
│       ├── CMakeLists.txt
│       ├── lib.cpp
│       ├── processor.cpp
│       └── handler.cpp
├── include/
│   ├── parser.hpp          # Single class, no subdirectory
│   ├── validator.hpp       # Single class, no subdirectory
│   ├── window/             # Compound word "mainwindow" → subdirectory
│   │   ├── lib.hpp
│   │   └── main.hpp        # MainWindow class
│   ├── component/
│   │   ├── lib.hpp
│   │   ├── controller.hpp
│   │   ├── service.hpp
│   │   └── manager/
│   │       ├── lib.hpp
│   │       └── cache.hpp
│   └── module/
│       ├── lib.hpp
│       ├── processor.hpp
│       └── handler.hpp
├── cmake/
│   └── CompileFont.cmake
├── CMakeLists.txt
└── CONVENTIONS.md

Multiple Project Directories

workspace/
├── projectname1/           # Root namespace = "projectname1"
│   ├── src/
│   │   ├── main.cpp
│   │   ├── parser.cpp
│   │   └── window/
│   │       └── main.cpp
│   ├── include/
│   │   ├── parser.hpp
│   │   └── window/
│   │       └── main.hpp
│   └── CMakeLists.txt
├── projectname2/           # Root namespace = "projectname2"
│   ├── src/
│   │   ├── main.cpp
│   │   ├── handler/
│   │   │   └── request.cpp
│   │   └── database/
│   │       └── connection.cpp
│   ├── include/
│   │   ├── handler/
│   │   │   └── request.hpp
│   │   └── database/
│   │       └── connection.hpp
│   └── CMakeLists.txt
└── projectname3/           # Root namespace = "projectname3"
    ├── src/
    │   ├── main.cpp
    │   └── engine/
    │       └── renderer.cpp
    ├── include/
    │   └── engine/
    │       └── renderer.hpp
    └── CMakeLists.txt

Namespace mapping examples:

Single project directory:

  • projectname::parser (from projectname/src/parser.cpp)
  • projectname::window::main (from projectname/src/window/main.cpp)
  • projectname::component::controller (from projectname/src/component/controller.cpp)
  • projectname::module::processor (from projectname/src/module/processor.cpp)

Multiple project directories (with workspace):

  • workspace::projectname1::parser (from workspace/projectname1/src/parser.cpp)
  • workspace::projectname1::window::main (from workspace/projectname1/src/window/main.cpp)
  • workspace::projectname2::handler::request (from workspace/projectname2/src/handler/request.cpp)
  • workspace::projectname2::database::connection (from workspace/projectname2/src/database/connection.cpp)
  • workspace::projectname3::engine::renderer (from workspace/projectname3/src/engine/renderer.cpp)

Class Name to Directory Mapping

Base Class to Directory Translation

When class names contain base class prefixes, remove the prefix and use the base class name as the directory:

Examples:

  • BaseObjectobject/ directory
  • BaseWidgetwidget/ directory
  • BaseWindowwindow/ directory
  • BaseDialogdialog/ directory
  • MainWindowwindow/main.hpp (compound word)
  • PushButtonbutton/push.hpp (compound word)

Mapping Rules:

  1. Single base class: BaseClassbaseclass/ directory
  2. Compound words: BaseClassSpecificbaseclass/specific.hpp
  3. Multiple related classes: Group by base class in same directory

Directory Structure Examples:

include/
├── object/                 # BaseObject-derived classes
│   ├── lib.hpp
│   ├── timer.hpp
│   └── thread.hpp
├── widget/                 # BaseWidget-derived classes
│   ├── lib.hpp
│   ├── button.hpp
│   ├── label.hpp
│   └── input.hpp
├── window/                 # BaseWindow-derived classes
│   ├── lib.hpp
│   ├── main.hpp           # MainWindow → window/main.hpp
│   └── dialog.hpp         # BaseDialog → window/dialog.hpp
└── handler/               # Handler-related classes
    ├── lib.hpp
    ├── request.hpp
    ├── response.hpp
    └── middleware.hpp

Namespace Mapping:

  • projectname::object::timer (from object/timer.hpp)
  • projectname::widget::button (from widget/button.hpp)
  • projectname::window::main (from window/main.hpp)
  • projectname::handler::request (from handler/request.hpp)

Framework Integration Guidelines

  • Identify base class patterns in your framework (Object, Widget, Window, etc.)
  • Create directories based on logical grouping of base classes
  • Use compound word rules for complex class names
  • Maintain consistent mapping across all framework-derived classes

Library Integration

  • Use appropriate design patterns for extensibility
  • Keep styling and configuration centralized
  • Follow established patterns for the target framework

Build System Guidelines

Qt Projects

  • Use qmake for Qt projects - qmake is simpler and more straightforward for Qt applications
  • Use Qt6 for new projects - Qt6 is the current stable version with modern C++ features
  • Avoid CMake for Qt projects - CMake can be overly complex for Qt development
  • qmake handles MOC, UIC, and RCC automatically - no manual configuration needed
  • qmake project files (.pro) are easier to maintain than CMakeLists.txt for Qt

qmake Build Instructions

Prerequisites

  • Qt6 development tools must be installed
  • qmake6 should be available in PATH
  • C++23 compatible compiler (clang++ or g++)

Standard Build Process

# Generate Makefile from .pro file
qmake6 projectname.pro

# Build with parallel compilation
make -j $(nproc)

# Run the application
./projectname

Build Directory Structure

projectname/
├── Makefile                # Generated by qmake6
├── projectname             # Executable (after build)
├── *.o                     # Object files
├── moc_*.cpp               # MOC generated files
├── src/                    # Source files
├── include/                # Header files
├── projectname.pro         # qmake project file
└── resources.qrc          # Qt resource file (if used)

Build Commands Reference

# Full build process
qmake6 projectname.pro
make -j $(nproc)

# Clean build
make clean

# Rebuild from scratch
make clean && make -j $(nproc)

# Debug build
qmake6 CONFIG+=debug projectname.pro
make -j $(nproc)

# Release build (default)
qmake6 CONFIG+=release projectname.pro
make -j $(nproc)

Parallel Build Optimization

  • Always use make -j $(nproc) for optimal build performance
  • $(nproc) automatically detects CPU cores for parallel compilation
  • Significantly faster builds on multi-core systems
  • Standard practice for all make-based builds

Troubleshooting

  • QMAKESPEC errors: Use qmake6 instead of qmake for Qt6 projects
  • Missing Qt6: Install Qt6 development packages for your system
  • Compiler errors: Ensure C++23 support and proper compiler flags in .pro file
  • MOC errors: qmake6 handles MOC automatically, check for missing Q_OBJECT macros

CMake Package Management

Package Discovery Priority

  1. find_package() - Use CMake's built-in package discovery first
  2. pkg-config - Use pkg-config as fallback when find_package fails
  3. Manual search - Only use find_library/find_path as last resort

pkg-config Configuration

# Set up pkg-config with proper search paths
find_package(PkgConfig REQUIRED)
set(ENV{PKG_CONFIG_PATH} "/usr/lib/pkgconfig/:/usr/local/lib/pkgconfig/")

# Example pkg-config usage
pkg_check_modules(MYLIB REQUIRED mylib)

Library Search Paths

# Set library search paths for find_library/find_path
set(CMAKE_LIBRARY_PATH "/usr/lib" "/usr/local/lib")
link_directories("/usr/lib" "/usr/local/lib")

Package Discovery Examples

# Preferred: Use find_package for standard libraries
find_package(OpenSSL REQUIRED)

# Fallback: Use pkg-config for custom libraries
pkg_check_modules(LIBYANG REQUIRED libyang)

# Last resort: Manual library search
find_library(PTHREAD_LIBRARY pthread)
if(PTHREAD_LIBRARY)
    set(PTHREAD_FOUND TRUE)
else()
    set(PTHREAD_FOUND FALSE)
endif()

Testing with ATF and Kyua

CMake Version Requirements

  • Minimum CMake version: 3.31.6
  • Use find_package() to check CMake version in CMakeLists.txt:
cmake_minimum_required(VERSION 3.31.6)

Test Framework Setup

  • ATF (Automated Testing Framework) for C++ test programs
  • Kyua for test execution and management
  • Lua format for Kyuafiles

OS-Specific Package Installation

Debian/Ubuntu:

# ATF C++ library and Kyua
sudo apt install libatf-c++-2 kyua

FreeBSD:

# ATF C++ library and Kyua
sudo pkg install atf kyua

Arch Linux:

# ATF C++ library and Kyua
sudo pacman -S atf kyua

Fedora/RHEL:

# ATF C++ library and Kyua
sudo dnf install atf-c++ kyua

macOS (Homebrew):

# ATF C++ library and Kyua
brew install atf kyua

Gentoo:

# ATF C++ library and Kyua
sudo emerge dev-libs/atf app-misc/kyua

ATF Test Program Structure

#include <atf-c++.hpp>

ATF_TEST_CASE(test_functionality);
ATF_TEST_CASE_BODY(test_functionality) {
    ATF_REQUIRE(condition);
    ATF_REQUIRE_EQ(expected, actual);
    ATF_PASS();
}

ATF_INIT_TEST_CASES(tcs) {
    ATF_ADD_TEST_CASE(tcs, test_functionality);
}

Kyuafile Configuration

Kyuafiles are Lua scripts that describe test suite structure. Every Kyuafile must start with a version declaration.

Basic Structure

-- Kyuafile in Lua format
syntax(2)

-- Define test suite name (optional)
test_suite('projectname')

-- Register ATF test programs
atf_test_program{name="test_program"}

Test Program Types

  • ATF test programs: Use atf_test_program{name="program_name"}
  • Plain test programs: Use plain_test_program{name="program_name"}
  • TAP test programs: Use tap_test_program{name="program_name"}

Metadata Properties

All test program types support optional metadata properties:

atf_test_program{name="test_program",
                 allowed_architectures='amd64 i386',
                 required_files='/bin/ls',
                 timeout=30,
                 required_user='root'}

Common metadata properties:

  • allowed_architectures - Whitespace-separated list of allowed architectures
  • allowed_platforms - Whitespace-separated list of allowed platforms
  • required_files - Space-separated list of required files
  • required_user - Required user privileges ('root', 'unprivileged')
  • timeout - Test timeout in seconds
  • execenv - Execution environment (platform-specific options)

Including Subdirectories

syntax(2)

-- Include other Kyuafiles (relative paths only)
include('module-1/Kyuafile')
include('module-2/Kyuafile')

Custom Properties

For properties with special characters (like dashes):

atf_test_program{name='the_test',
                 ['custom.Bug-Id']='category/12345'}

Platform-Specific Execution Environments

-- Example for systems supporting jail/container environments
atf_test_program{name='network_test',
                 execenv='jail',
                 execenv_jail_params='vnet allow.raw_sockets',
                 required_user='root'}

-- Example for privilege requirements
atf_test_program{name='privileged_test',
                 required_user='root'}

CMake Test Integration

# Create test executable
add_executable(test_program test_program.cpp)

# Link ATF library
find_package(PkgConfig REQUIRED)
pkg_check_modules(ATF REQUIRED atf-c++)
target_link_libraries(test_program ${ATF_LIBRARIES})
target_include_directories(test_program PRIVATE ${ATF_INCLUDE_DIRS})

# Set test output to build root
set_target_properties(test_program PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
)

# Copy Kyuafile to build directory (always updated)
configure_file(
    ${CMAKE_SOURCE_DIR}/tests/Kyuafile
    ${CMAKE_BINARY_DIR}/Kyuafile
    COPYONLY
)

# Create test target (not default)
add_custom_target(run_tests
    COMMAND kyua test
    DEPENDS test_program
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    COMMENT "Running ATF tests with Kyua"
)

Test Directory Structure

project/
├── test/                           # Test directory adjacent to src/ and include/
│   ├── Kyuafile                    # Test configuration in Lua format
│   ├── parser.cpp                  # Test for src/parser.cpp
│   ├── validator.cpp               # Test for src/validator.cpp
│   ├── window/                     # Mirrors src/window/ structure
│   │   └── main.cpp                # Test for src/window/main.cpp
│   ├── component/                  # Mirrors src/component/ structure
│   │   ├── controller.cpp          # Test for src/component/controller.cpp
│   │   ├── service.cpp             # Test for src/component/service.cpp
│   │   └── manager/                # Mirrors src/component/manager/ structure
│   │       └── cache.cpp           # Test for src/component/manager/cache.cpp
│   └── module/                     # Mirrors src/module/ structure
│       ├── processor.cpp           # Test for src/module/processor.cpp
│       └── handler.cpp             # Test for src/module/handler.cpp
└── CMakeLists.txt

Test Directory Rules

  • test/ directory is adjacent to src/ and include/
  • Mirror src/ structure - test subdirectories follow the same structure as src/ subdirectories
  • Test file naming - use the same filename as the source file being tested
  • Namespace mapping - test files use the same namespace as their corresponding source files

Running Tests

# Build tests (not part of default build)
make run_tests

# Run tests manually
cd build/
kyua test

# Run specific test
kyua test test_program:test_functionality

# View test results (after running tests)
kyua report --verbose

# View summary only
kyua report

ATF Macros and Functions

  • ATF_REQUIRE(condition) - Fail test if condition is false
  • ATF_REQUIRE_EQ(expected, actual) - Require equality
  • ATF_CHECK(condition) - Check condition but continue on failure
  • ATF_PASS() - Mark test as passed
  • ATF_FAIL(reason) - Mark test as failed with reason
  • ATF_SKIP(reason) - Skip test with reason

Manual Page References

When you get lost with the testing framework, consult these manual pages:

  • man kyuafile - Kyuafile syntax and configuration options
  • man kyua - Kyua test runner commands and options
  • man atf-c++ - ATF C++ library macros and functions

Quick reference commands:

man kyuafile    # Kyuafile configuration syntax
man kyua        # Test execution commands
man atf-c++     # ATF C++ programming interface

Test Quality Guidelines

  • Write meaningful tests - Test actual functionality, not trivial operations
  • Avoid false positives - Tests should fail when code is broken
  • Test edge cases - Include boundary conditions and error scenarios
  • Verify expected behavior - Use ATF_REQUIRE_EQ for specific value validation
  • Test error conditions - Verify proper error handling and exceptions

Quality Assurance

  • All code must compile with C++23 standard
  • Follow these conventions strictly
  • Update this document when conventions change
  • Document any exceptions to these rules
  • Tests must be runnable with Kyua framework when requested

Error Handling Conventions

Error File Organization

SPECIAL CASE: Error classes follow a different pattern than other classes:

  • Single file approach: src/exception.cpp and include/exception.hpp
  • Base error class: ProjectNameBaseError
  • All errors in same files: Each subsequent error extends the base class in the same files
  • No subdirectories: Even if multiple error classes exist

Example structure:

src/exception.cpp          # All error implementations
include/exception.hpp      # All error declarations

Example error hierarchy:

// include/exception.hpp
namespace projectname {
    class ProjectNameBaseError : public std::exception {
        // base implementation
    };
    
    class ValidationError : public ProjectNameBaseError {
        // validation-specific implementation
    };
    
    class NetworkError : public ProjectNameBaseError {
        // network-specific implementation
    };
}

Rationale: Errors are typically related and benefit from being in the same files for easier maintenance and consistent error handling patterns.

Async/Threading Conventions

Preferred Approach

  • Use std::async over std::thread whenever possible
  • Avoid excessive async usage - C++ async is not always intuitive
  • Use async when it truly makes sense - for actual asynchronous operations

Guidelines

  • Prefer std::async for most concurrent operations
  • Use std::thread only when std::async is not suitable
  • Avoid over-asyncification - not everything needs to be async
  • Consider readability - sometimes synchronous code is clearer
  • Use async judiciously - only when there's genuine benefit from concurrency

When to Use Async

  • I/O operations that can block
  • CPU-intensive tasks that can run in parallel
  • Independent operations that don't need to wait for each other
  • Operations that benefit from non-blocking execution

When to Avoid Async

  • Simple operations that don't benefit from concurrency
  • Operations with complex interdependencies
  • When synchronous code is more readable and maintainable
  • Over-engineering simple functionality

System Call Guidelines

Prefer Libraries Over System Calls

  • Use library APIs instead of system calls when libraries are available
  • Avoid system(), popen(), exec() family as substitutes for proper library calls
  • Use native library functions for better error handling, portability, and security

Examples

❌ Avoid system calls when libraries exist:

// ❌ Bad - using system() when library exists
system("curl -s https://api.example.com/data");

// ❌ Bad - using popen() when HTTP library exists  
FILE* pipe = popen("wget -qO- https://api.example.com/data", "r");

// ❌ Bad - using exec() when JSON library exists
execl("/usr/bin/jq", "jq", ".data", "input.json", NULL);

✅ Use proper libraries:

// ✅ Good - use HTTP library (libcurl, httplib, etc.)
auto response = http_client.get("https://api.example.com/data");

// ✅ Good - use JSON library (nlohmann/json, rapidjson, etc.)
auto data = json::parse(file_content);
auto result = data["data"];

// ✅ Good - use native C++ file operations
std::ifstream file("input.json");
json data;
file >> data;

Benefits of Using Libraries

  • Better error handling - proper exception handling and return codes
  • Cross-platform compatibility - libraries handle OS differences
  • Security - avoid shell injection vulnerabilities from system calls
  • Performance - no process spawning overhead
  • Integration - better integration with C++ code and memory management

String Literal Guidelines

Prefer constexpr Raw Strings for Complex Text

  • Use constexpr raw strings for escape characters, query strings, and text blobs
  • Define in header files for compile-time availability
  • Use raw string literals to avoid escape character issues

Examples

❌ Avoid escaped strings:

// ❌ Bad - difficult to read and maintain
const std::string query = "SELECT * FROM users WHERE name = 'John\\'s Data' AND status = \"active\"";
const std::string json = "{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}";
const std::string regex = "\\d{4}-\\d{2}-\\d{2}";

✅ Use constexpr raw strings in headers:

// ✅ Good - defined in header file
constexpr auto SQL_QUERY = R"(
    SELECT * FROM users 
    WHERE name = 'John's Data' 
    AND status = "active"
)";

constexpr auto USER_JSON_TEMPLATE = R"({
    "name": "John",
    "age": 30,
    "city": "New York"
})";

constexpr auto DATE_REGEX = R"(\d{4}-\d{2}-\d{2})";

constexpr auto HTML_TEMPLATE = R"(
    <html>
        <body>
            <h1>Welcome</h1>
            <p>Hello, {{name}}!</p>
        </body>
    </html>
)";

Benefits of constexpr Raw Strings

  • Compile-time evaluation - no runtime string construction
  • Readable format - preserves original formatting and indentation
  • No escape issues - raw strings handle special characters naturally
  • Header placement - available at compile time across translation units
  • Maintainable - easy to edit complex strings without escape character confusion

Regex and String Processing Guidelines

Prefer std::regex Over String Operations

  • Use std::regex for pattern matching instead of manual string operations
  • Define regex patterns as constexpr raw strings in headers
  • Use regex for validation, parsing, and text processing whenever possible

Examples

❌ Avoid manual string operations:

// ❌ Bad - manual string parsing and validation
bool isValidEmail(const std::string& email) {
    if (email.find('@') == std::string::npos) return false;
    if (email.find('.') == std::string::npos) return false;
    // More manual checks...
    return true;
}

std::string extractNumber(const std::string& text) {
    size_t start = text.find_first_of("0123456789");
    if (start == std::string::npos) return "";
    size_t end = text.find_first_not_of("0123456789", start);
    return text.substr(start, end - start);
}

✅ Use std::regex:

// ✅ Good - defined in header file
constexpr auto EMAIL_REGEX = R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})";
constexpr auto NUMBER_REGEX = R"(\d+)";
constexpr auto DATE_REGEX = R"(\d{4}-\d{2}-\d{2})";
constexpr auto IP_ADDRESS_REGEX = R"((\d{1,3}\.){3}\d{1,3})";

// ✅ Good - use std::regex for validation and parsing
bool isValidEmail(const std::string& email) {
    static const std::regex emailPattern(EMAIL_REGEX);
    return std::regex_match(email, emailPattern);
}

std::string extractNumber(const std::string& text) {
    static const std::regex numberPattern(NUMBER_REGEX);
    std::smatch match;
    if (std::regex_search(text, match, numberPattern)) {
        return match.str();
    }
    return "";
}

std::vector<std::string> findDates(const std::string& text) {
    static const std::regex datePattern(DATE_REGEX);
    std::vector<std::string> dates;
    std::sregex_iterator begin(text.begin(), text.end(), datePattern);
    std::sregex_iterator end;
    for (auto it = begin; it != end; ++it) {
        dates.push_back(it->str());
    }
    return dates;
}

Benefits of std::regex

  • Robust pattern matching - handles complex patterns reliably
  • Standard library - no external dependencies
  • Performance - optimized regex engine
  • Maintainable - clear pattern definitions
  • Flexible - supports various regex operations (match, search, replace)

File Creation Guidelines

Only Create When Explicitly Requested

  • README.md files - Only create when specifically asked
  • Shell scripts - Only create when specifically requested
  • Test files - Only create when explicitly asked for ATF tests
  • Documentation files - Only create when specifically requested

Required Files

  • .gitignore files - Always create for all projects to exclude build artifacts, temporary files, and IDE-specific files

.gitignore Contents

# Build artifacts
build/
dist/
out/

# CMake artifacts
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
*.cmake
!CMakeLists.txt

# qmake artifacts
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
object_script_*.Release
object_script_*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user.*
*.autosave

# Compiled Object files
*.o
*.obj
*.so
*.dylib
*.dll
*.exe
*.out
*.app

# IDE and Editor files
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
Thumbs.db

# Temporary files
*.tmp
*.temp
*.log
*.pid
*.seed
*.pid.lock

# Dependency directories
node_modules/
vendor/

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

Default Behavior

  • Focus on implementing the requested functionality
  • Follow conventions for existing files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment