- π― Goal
- β¨ Features
- π Quick Start
- π¦ Installation Methods
- π API Reference
- βοΈ Configuration Files
- π Environment Variables
- π¨ Common Patterns
β οΈ Error Handling- β Best Practices
- π§ Troubleshooting
- π» Compilation
- π€ Examples
- π¨ Compiler Support
- π¦ Dependencies
- β‘ Performance
- β FAQ
- π€ Contributing
- π License
Config C++ provides hierarchical configuration management for C++ applications, inspired by node-config. It allows you to:
- π Define default configurations and override them for different environments (dev, staging, production)
- π Support multiple configuration file formats (JSON, YAML, XML)
- π Override configuration using environment variables
- π Type-safe configuration access with compile-time checking
- π― Hierarchical configuration with dot-notation paths
β
Multiple file formats: JSON, YAML, XML
β
Environment-based configs: Automatic loading based on CXX_ENV
β
Type-safe API: Template-based configuration access
β
Environment variable overrides: Use env vars to override any config value
β
Hierarchical structure: Access nested values with dot notation (db.host)
β
Thread-safe: Safe to use across multiple threads
β
Header-only option: Easy integration into your project
β
Zero dependencies: Except for file format parsers (bundled)
Add config-cxx to your project using git submodules:
mkdir externals && cd externals
git submodule add https://github.com/cieslarmichal/config-cxx.git
git submodule update --init --recursive- Create configuration directory:
mkdir config- Create default configuration (
config/default.json):
{
"db": {
"name": "users",
"host": "localhost",
"port": 3306,
"user": "default",
"password": "default"
},
"server": {
"port": 8080,
"host": "0.0.0.0"
}
}- Create production overrides (
config/production.json):
{
"db": {
"host": "prod-db.example.com",
"user": "admin",
"password": "secretpassword"
},
"server": {
"port": 443
}
}- Link with your project (CMakeLists.txt):
set(CONFIG_BUILD_TESTING OFF)
add_subdirectory(externals/config-cxx)
add_executable(myapp main.cpp)
target_link_libraries(myapp config-cxx)#include "config-cxx/config.h"
#include <iostream>
int main() {
// Config automatically loads based on CXX_ENV
config::Config config;
// Get configuration values with type safety
auto dbHost = config.get<std::string>("db.host");
auto dbPort = config.get<int>("db.port");
auto dbName = config.get<std::string>("db.name");
std::cout << "Connecting to " << dbHost << ":" << dbPort
<< "/" << dbName << std::endl;
// Check if a key exists before accessing
if (config.has("redis.host")) {
auto redisHost = config.get<std::string>("redis.host");
std::cout << "Redis enabled at " << redisHost << std::endl;
}
// Use getOptional for values that might not exist
auto cacheTimeout = config.getOptional<int>("cache.timeout");
if (cacheTimeout.has_value()) {
std::cout << "Cache timeout: " << *cacheTimeout << std::endl;
}
return 0;
}Run your application:
# Development (uses default.json + development.json)
export CXX_ENV=development
./myapp
# Production (uses default.json + production.json)
export CXX_ENV=production
./myappBest for projects using git:
# In your project root
mkdir externals && cd externals
git submodule add https://github.com/cieslarmichal/config-cxx.git
git submodule update --init --recursiveCMakeLists.txt:
set(CONFIG_BUILD_TESTING OFF)
add_subdirectory(externals/config-cxx)
target_link_libraries(your_target config-cxx)Modern CMake approach (CMake 3.14+):
include(FetchContent)
FetchContent_Declare(
config-cxx
GIT_REPOSITORY https://github.com/cieslarmichal/config-cxx.git
GIT_TAG main # or specific version tag
)
set(CONFIG_BUILD_TESTING OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(config-cxx)
target_link_libraries(your_target config-cxx)- Download the latest release
- Extract to your project
- Add to CMake:
add_subdirectory(path/to/config-cxx)
target_link_libraries(your_target config-cxx)The Config class is the main entry point for accessing configuration values.
#include "config-cxx/config.h"
namespace config {
class Config {
public:
template <typename T>
T get(const std::string& keyPath);
template <typename T>
std::optional<T> getOptional(const std::string& keyPath);
ConfigValue get(const std::string& keyPath);
bool has(const std::string& keyPath);
};
}Get a configuration value by path. Throws exception if key doesn't exist.
template <typename T>
T get(const std::string& keyPath);Parameters:
keyPath: Dot-separated path to the configuration value (e.g.,"db.host")
Returns: Value cast to type T
Throws: std::runtime_error if key doesn't exist or type conversion fails
Examples:
config::Config config;
// Get string values
auto dbHost = config.get<std::string>("db.host"); // "localhost"
auto apiUrl = config.get<std::string>("api.baseUrl"); // "https://api.example.com"
// Get numeric values
auto dbPort = config.get<int>("db.port"); // 3306
auto timeout = config.get<double>("request.timeout"); // 30.5
auto maxRetries = config.get<int>("retry.max"); // 3
// Get boolean values
auto sslEnabled = config.get<bool>("ssl.enabled"); // true
auto debugMode = config.get<bool>("debug"); // false
// Get arrays
auto allowedHosts = config.get<std::vector<std::string>>("security.allowedHosts");
// ["localhost", "example.com", "*.example.org"]
// Nested objects
auto smtpHost = config.get<std::string>("email.smtp.host");
auto smtpPort = config.get<int>("email.smtp.port");Get a configuration value that might not exist. Returns std::nullopt if key doesn't exist.
template <typename T>
std::optional<T> getOptional(const std::string& keyPath);Parameters:
keyPath: Dot-separated path to the configuration value
Returns: std::optional<T> containing the value or std::nullopt
Examples:
config::Config config;
// Safe access to optional values
auto redisHost = config.getOptional<std::string>("redis.host");
if (redisHost.has_value()) {
std::cout << "Redis configured at: " << *redisHost << std::endl;
} else {
std::cout << "Redis not configured, using default cache" << std::endl;
}
// With default values
auto cacheTimeout = config.getOptional<int>("cache.timeout").value_or(300);
auto maxConnections = config.getOptional<int>("pool.max").value_or(10);
// Chain multiple optional configs
auto emailEnabled = config.getOptional<bool>("email.enabled").value_or(false);
if (emailEnabled) {
auto smtpHost = config.get<std::string>("email.smtp.host");
// Process email configuration
}Check if a configuration key exists.
bool has(const std::string& keyPath);Parameters:
keyPath: Dot-separated path to check
Returns: true if key exists, false otherwise
Examples:
config::Config config;
// Check before accessing
if (config.has("db.host")) {
auto host = config.get<std::string>("db.host");
// Use host
}
// Conditional feature enablement
if (config.has("features.newFeature")) {
bool enabled = config.get<bool>("features.newFeature");
if (enabled) {
// Enable new feature
}
}
// Validate required configuration
std::vector<std::string> requiredKeys = {
"db.host", "db.port", "db.name"
};
for (const auto& key : requiredKeys) {
if (!config.has(key)) {
throw std::runtime_error("Missing required config: " + key);
}
}Config-cxx supports the following types:
| Type | Example | Description |
|---|---|---|
std::string |
"localhost" |
Text values |
int |
3306 |
Integer numbers |
double |
30.5 |
Floating-point numbers |
float |
1.5f |
Single-precision floats |
bool |
true, false |
Boolean values |
std::vector<std::string> |
["a", "b"] |
String arrays |
ConfigValue |
(variant) | Untyped access |
Config-cxx reads configuration files from the ./config directory relative to your application's working directory.
Custom directory:
export CXX_CONFIG_DIR=/path/to/config
# Or relative path
export CXX_CONFIG_DIR=./my-configs
export CXX_CONFIG_DIR=../shared-configsConfiguration files are loaded and merged in this order (later files override earlier ones):
1. default.{EXT} # Base configuration
2. {deployment}.{EXT} # Environment-specific (e.g., production.json)
3. local.{EXT} # Local overrides (not in version control)
4. local-{deployment}.{EXT} # Local environment-specific
5. custom-environment-variables.{EXT} # Environment variable mappings
Where:
{EXT}can be:.json,.yaml,.yml, or.xml{deployment}comes from theCXX_ENVenvironment variable (default:"development")
Example with CXX_ENV=production:
config/
βββ default.json # β
Loaded (base)
βββ development.json # β Skipped
βββ production.json # β
Loaded (overrides default)
βββ local.json # β
Loaded (if exists)
βββ local-production.json # β
Loaded (if exists)
βββ custom-environment-variables.json # β
Loaded (if exists)
Merge behavior:
// default.json
{
"db": {
"host": "localhost",
"port": 3306,
"name": "users"
}
}
// production.json
{
"db": {
"host": "prod-db.example.com",
"password": "secret"
}
}
// Result:
{
"db": {
"host": "prod-db.example.com", // From production.json
"port": 3306, // From default.json
"name": "users", // From default.json
"password": "secret" // From production.json
}
}{
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp",
"ssl": true,
"poolSize": 10
},
"cache": {
"enabled": true,
"ttl": 3600
},
"allowedOrigins": ["http://localhost:3000", "https://example.com"]
}database:
host: localhost
port: 5432
name: myapp
ssl: true
poolSize: 10
cache:
enabled: true
ttl: 3600
allowedOrigins:
- http://localhost:3000
- https://example.com<?xml version="1.0" encoding="UTF-8"?>
<config>
<database>
<host>localhost</host>
<port>5432</port>
<name>myapp</name>
<ssl>true</ssl>
<poolSize>10</poolSize>
</database>
<cache>
<enabled>true</enabled>
<ttl>3600</ttl>
</cache>
</config>Local files (local.json, local-{deployment}.json) are intended for:
- β Developer-specific overrides
- β CI/CD pipeline configurations
- β Secrets that shouldn't be in version control
- β Machine-specific settings
.gitignore:
config/local.json
config/local-*.jsonBest practice:
# β Avoid: Global local file affects all environments
config/local.json
# β
Prefer: Environment-specific local files
config/local-development.json
config/local-production.jsonThis prevents issues where tests pass locally but fail in CI/CD.
Specifies the deployment environment. Determines which configuration files are loaded.
export CXX_ENV=productionCommon values:
development(default)testortestingstagingorstageproductionorprod
Example:
# Development
export CXX_ENV=development
./myapp # Loads: default.json + development.json
# Production
export CXX_ENV=production
./myapp # Loads: default.json + production.jsonSpecifies the directory containing configuration files.
# Absolute path
export CXX_CONFIG_DIR=/etc/myapp/config
# Relative path
export CXX_CONFIG_DIR=./configs
export CXX_CONFIG_DIR=../shared-configDefault: ./config (relative to working directory)
Override any configuration value using environment variables.
1. Create mapping file (config/custom-environment-variables.json):
{
"db": {
"host": "DB_HOST",
"port": "DB_PORT",
"password": "DB_PASSWORD"
},
"api": {
"key": "API_KEY"
},
"features": {
"newFeature": "ENABLE_NEW_FEATURE"
}
}2. Set environment variables:
export DB_HOST=prod-db.example.com
export DB_PORT=5432
export DB_PASSWORD=supersecret
export API_KEY=abc123xyz
export ENABLE_NEW_FEATURE=true3. Access in code:
config::Config config;
// These values come from environment variables
auto dbHost = config.get<std::string>("db.host"); // "prod-db.example.com"
auto dbPort = config.get<int>("db.port"); // 5432
auto dbPassword = config.get<std::string>("db.password"); // "supersecret"
auto apiKey = config.get<std::string>("api.key"); // "abc123xyz"Precedence (highest to lowest):
- π₯ Custom environment variables
- π₯
local-{deployment}.json - π₯
local.json - π
{deployment}.json - π
default.json
class ConfigValidator {
public:
static void validate(const config::Config& cfg) {
// Check required keys
std::vector<std::string> required = {
"db.host", "db.port", "db.name",
"server.port", "api.key"
};
for (const auto& key : required) {
if (!cfg.has(key)) {
throw std::runtime_error("Missing required config: " + key);
}
}
// Validate ranges
auto serverPort = cfg.get<int>("server.port");
if (serverPort < 1 || serverPort > 65535) {
throw std::runtime_error("Invalid server port");
}
}
};
int main() {
config::Config config;
ConfigValidator::validate(config);
// Continue with validated configuration
}struct DatabaseConfig {
std::string host;
int port;
std::string name;
std::string user;
std::string password;
static DatabaseConfig fromConfig(const config::Config& cfg) {
return DatabaseConfig{
.host = cfg.get<std::string>("db.host"),
.port = cfg.get<int>("db.port"),
.name = cfg.get<std::string>("db.name"),
.user = cfg.get<std::string>("db.user"),
.password = cfg.get<std::string>("db.password")
};
}
};
int main() {
config::Config config;
auto dbConfig = DatabaseConfig::fromConfig(config);
// Use strongly-typed config object
connectToDatabase(dbConfig);
}class FeatureFlags {
config::Config& cfg;
public:
explicit FeatureFlags(config::Config& config) : cfg(config) {}
bool isEnabled(const std::string& feature) const {
auto key = "features." + feature;
return cfg.getOptional<bool>(key).value_or(false);
}
};
int main() {
config::Config config;
FeatureFlags features(config);
if (features.isEnabled("newUI")) {
// Enable new UI
}
if (features.isEnabled("betaFeature")) {
// Enable beta feature
}
}enum class Environment { Development, Staging, Production };
Environment getCurrentEnvironment() {
const char* env = std::getenv("CXX_ENV");
if (!env) return Environment::Development;
std::string envStr(env);
if (envStr == "production" || envStr == "prod") {
return Environment::Production;
} else if (envStr == "staging" || envStr == "stage") {
return Environment::Staging;
}
return Environment::Development;
}
int main() {
config::Config config;
auto env = getCurrentEnvironment();
if (env == Environment::Production) {
// Production-specific initialization
enableStrictSSL();
disableDebugLogging();
} else if (env == Environment::Development) {
// Development-specific initialization
enableDebugLogging();
enableHotReload();
}
}class ConfigManager {
public:
static ConfigManager& instance() {
static ConfigManager instance;
return instance;
}
config::Config& getConfig() {
return cfg;
}
// Delete copy and move constructors
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
private:
ConfigManager() = default;
config::Config cfg;
};
// Usage
int main() {
auto& config = ConfigManager::instance().getConfig();
auto dbHost = config.get<std::string>("db.host");
}Config-cxx throws std::runtime_error in the following cases:
Thrown when: Accessing a non-existent configuration key with get<T>()
config::Config config;
try {
auto value = config.get<std::string>("nonexistent.key");
} catch (const std::runtime_error& e) {
std::cerr << "Error: " << e.what() << std::endl;
// Error: Configuration key 'nonexistent.key' not found
}Solution: Use has() or getOptional():
// Option 1: Check first
if (config.has("optional.key")) {
auto value = config.get<std::string>("optional.key");
}
// Option 2: Use getOptional
auto value = config.getOptional<std::string>("optional.key");
if (value.has_value()) {
// Use *value
}Thrown when: Requested type doesn't match the configuration value type
// Config: { "port": "not-a-number" }
try {
auto port = config.get<int>("port");
} catch (const std::runtime_error& e) {
std::cerr << "Type conversion error: " << e.what() << std::endl;
}Thrown when: Configuration file has syntax errors
// Invalid JSON
{
"db": {
"host": "localhost",
"port": 3306, // Missing closing brace
}Error prevention:
// Validate JSON before deployment
// Use a JSON validator or linterclass SafeConfig {
config::Config& cfg;
public:
explicit SafeConfig(config::Config& config) : cfg(config) {}
template<typename T>
std::optional<T> getSafe(const std::string& key) {
try {
if (cfg.has(key)) {
return cfg.get<T>(key);
}
} catch (const std::exception& e) {
std::cerr << "Config error for key '" << key << "': "
<< e.what() << std::endl;
}
return std::nullopt;
}
};int main() {
try {
config::Config config;
// Validate critical configuration early
std::vector<std::string> criticalKeys = {
"db.host", "db.port", "api.key"
};
for (const auto& key : criticalKeys) {
if (!config.has(key)) {
std::cerr << "FATAL: Missing critical config: " << key << std::endl;
return 1;
}
}
// Access with proper error handling
auto dbHost = config.get<std::string>("db.host");
auto dbPort = config.get<int>("db.port");
// Use optional for non-critical config
auto logLevel = config.getOptional<std::string>("log.level")
.value_or("info");
// Run application
runApplication(dbHost, dbPort, logLevel);
} catch (const std::exception& e) {
std::cerr << "Configuration error: " << e.what() << std::endl;
return 1;
}
return 0;
}// β
Good: Type-safe
auto port = config.get<int>("server.port");
// β Avoid: Untyped access
auto portValue = config.get("server.port");
// Returns ConfigValue variant, requires manual type checking// β
Good: Fail fast with clear errors
void validateConfig(const config::Config& cfg) {
if (!cfg.has("db.host")) {
throw std::runtime_error("Missing required config: db.host");
}
auto port = cfg.get<int>("server.port");
if (port < 1 || port > 65535) {
throw std::runtime_error("Invalid port number");
}
}
int main() {
config::Config config;
validateConfig(config); // Validate before running
runApplication();
}config/
βββ default.json # β
Base configuration (committed)
βββ development.json # β
Dev overrides (committed)
βββ production.json # β
Production overrides (committed)
βββ local-development.json # β
Local dev settings (gitignored)
βββ local.json # β Avoid: Affects all environments
// β Bad: Secrets in version control
{
"db": {
"password": "supersecret123"
}
}
// β
Good: Use environment variables
// config/custom-environment-variables.json
{
"db": {
"password": "DB_PASSWORD"
}
}{
"server": {
"port": 8080, // HTTP server port
"host": "0.0.0.0", // Bind to all interfaces
"workers": 4 // Number of worker threads
},
"db": {
"poolSize": 10, // Connection pool size
"timeout": 5000 // Query timeout in milliseconds
}
}// β
Good: Clear and descriptive
{
"database": {
"connectionTimeout": 5000,
"maxRetries": 3
}
}
// β Bad: Unclear abbreviations
{
"db": {
"ct": 5000,
"mr": 3
}
}// β
Good: Fallback to reasonable defaults
auto logLevel = config.getOptional<std::string>("log.level")
.value_or("info");
auto maxConnections = config.getOptional<int>("pool.max")
.value_or(10);
// β Bad: No defaults for optional settings
auto logLevel = config.get<std::string>("log.level"); // Crashes if missing// β
Good: Organized hierarchy
{
"database": {
"primary": {
"host": "db1.example.com",
"port": 5432
},
"replica": {
"host": "db2.example.com",
"port": 5432
}
},
"cache": {
"redis": {
"host": "cache.example.com",
"port": 6379
}
}
}
// β Bad: Flat structure
{
"db_primary_host": "db1.example.com",
"db_primary_port": 5432,
"db_replica_host": "db2.example.com",
"cache_redis_host": "cache.example.com"
}Symptoms: Default values are used, environment-specific configs ignored
Solutions:
# 1. Check CXX_ENV is set
echo $CXX_ENV
# 2. Check config directory exists
ls -la config/
# 3. Verify file names match CXX_ENV
# If CXX_ENV=production, need config/production.json
# 4. Check file permissions
ls -l config/*.json
# 5. Enable debug logging (if available)
export CXX_DEBUG=1Symptoms: Runtime error when accessing config
Solutions:
// 1. Check if key exists
if (!config.has("db.host")) {
std::cerr << "db.host is not configured" << std::endl;
}
// 2. Use getOptional for optional keys
auto redisHost = config.getOptional<std::string>("redis.host");
// 3. Verify JSON structure
{
"db": {
"host": "localhost" // Access as "db.host", not "host"
}
}
// 4. Check for typos
config.get<std::string>("databse.host"); // β Typo
config.get<std::string>("database.host"); // β
CorrectSymptoms: Runtime error when getting config value
Solutions:
// Check actual type in config file
{
"port": "3306" // β String
}
// Should be:
{
"port": 3306 // β
Number
}
// Or handle as string and convert
auto portStr = config.get<std::string>("port");
int port = std::stoi(portStr);Symptoms: Env vars set but config uses file values
Solutions:
# 1. Create custom-environment-variables.json
cat config/custom-environment-variables.json
# 2. Verify environment variable is set
echo $DB_HOST
# 3. Check variable name matches mapping
# config/custom-environment-variables.json:
{
"db": {
"host": "DB_HOST" // Must match env var name exactly
}
}
# 4. Restart application after setting env vars
export DB_HOST=newhost
./myapp # Restart requiredSymptoms: "Config directory not found" error
Solutions:
# 1. Check current working directory
pwd
# 2. Config directory should be relative to working directory
ls config/ # Should show config files
# 3. Or set CXX_CONFIG_DIR
export CXX_CONFIG_DIR=/absolute/path/to/config
# 4. Or use relative path
export CXX_CONFIG_DIR=./my-config-dirSymptoms: Linker errors or missing headers
Solutions:
# 1. Ensure submodules are initialized
git submodule update --init --recursive
# 2. Check CMakeLists.txt
add_subdirectory(externals/config-cxx)
target_link_libraries(your_target config-cxx)
# 3. Verify include path
#include "config-cxx/config.h" // β
Correct
#include "Config.h" // β Wrong
# 4. Check compiler version
g++ --version # Should be 13+
clang++ --version # Should be 16+For detailed build instructions, see BUILDING.md
# Clone and prepare
git clone https://github.com/cieslarmichal/config-cxx.git
cd config-cxx
git submodule update --init --recursive
# Build
mkdir build && cd build
cmake ..
cmake --build .
# Run tests
./config-cxx-UT- CMake: 3.14 or newer
- C++ Standard: C++20
- Supported Compilers:
- GCC 13+
- Clang 16+
- Apple Clang 16+
- MSVC 143+ (Visual Studio 2022)
For detailed platform-specific build instructions, including:
- Installing compilers and dependencies
- Platform-specific configuration options
- Troubleshooting common build issues
- Docker builds
- CMake options
π See BUILDING.md for complete build instructions
# Disable tests (reduces dependencies)
set(CONFIG_BUILD_TESTING OFF)
# Example CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(MyApp)
set(CMAKE_CXX_STANDARD 20)
set(CONFIG_BUILD_TESTING OFF)
add_subdirectory(externals/config-cxx)
add_executable(myapp src/main.cpp)
target_link_libraries(myapp config-cxx)Full example available in examples/project
Configuration files:
examples/project/config/
βββ default.json
βββ development.json
βββ production.json
βββ custom-environment-variables.json
Source: examples/project/src/Main.cpp
#include "config-cxx/config.h"
#include <iostream>
int main() {
config::Config config;
// Database configuration
const auto dbHost = config.get<std::string>("db.host");
const auto dbPort = config.get<int>("db.port");
// AWS configuration
const auto awsAccountId = config.get<std::string>("aws.accountId");
const auto awsRegion = config.get<std::string>("aws.region");
// Authentication settings
const auto authExpiresIn = config.get<int>("auth.expiresIn");
const auto authEnabled = config.get<bool>("auth.enabled");
const auto authRoles = config.get<std::vector<std::string>>("auth.roles");
std::cout << "Database: " << dbHost << ":" << dbPort << std::endl;
std::cout << "AWS Region: " << awsRegion << std::endl;
std::cout << "Auth enabled: " << (authEnabled ? "yes" : "no") << std::endl;
return 0;
}{
"server": {
"host": "0.0.0.0",
"port": 8080,
"workerThreads": 4,
"requestTimeout": 30000,
"maxRequestSize": 10485760
},
"ssl": {
"enabled": false,
"certPath": "",
"keyPath": ""
},
"cors": {
"enabled": true,
"allowedOrigins": ["http://localhost:3000"]
}
}{
"service": {
"name": "user-service",
"version": "1.0.0",
"environment": "development"
},
"database": {
"primary": {
"host": "localhost",
"port": 5432,
"name": "users",
"poolSize": 10
},
"replica": {
"host": "localhost",
"port": 5433,
"name": "users"
}
},
"cache": {
"redis": {
"host": "localhost",
"port": 6379,
"ttl": 3600
}
},
"messaging": {
"rabbitmq": {
"host": "localhost",
"port": 5672,
"vhost": "/"
}
}
}| Compiler | Minimum Version | Recommended |
|---|---|---|
| GCC | 13.0 | 13.2+ |
| Clang | 16.0 | 17.0+ |
| Apple Clang | 16.0 | Latest |
| MSVC | 19.34 (VS 2022) | Latest |
C++ Standard: C++20 required
- nlohmann/json (included via submodule)
- yaml-cpp (included via submodule)
- pugixml (included via submodule)
- Google Test (optional, disable with
CONFIG_BUILD_TESTING=OFF)
All dependencies are included as git submodules - no manual installation required.
Config-cxx is thread-safe:
- β
Multiple threads can safely call
get(),getOptional(), andhas()simultaneously - β Internal mutex protects configuration data access
- β No external synchronization needed
- β Read operations are lock-free after initialization
// Safe to use from multiple threads
config::Config config; // Initialize once
// Thread 1
auto dbHost = config.get<std::string>("db.host");
// Thread 2 (concurrent access is safe)
auto dbPort = config.get<int>("db.port");- Initialization: O(n) where n is the number of configuration keys
- get() operation: O(1) average case (hash map lookup)
- has() operation: O(1) average case
- Memory usage: Minimal - configurations are loaded once at startup
// β
Good: Initialize once, reuse
config::Config config;
auto host = config.get<std::string>("db.host");
auto port = config.get<int>("db.port");
// β Avoid: Creating multiple Config instances
for (int i = 0; i < 1000; i++) {
config::Config config; // Reloads files each time!
auto value = config.get<std::string>("key");
}
// β
Good: Cache frequently accessed values
struct AppConfig {
std::string dbHost;
int dbPort;
static AppConfig load() {
config::Config cfg;
return {cfg.get<std::string>("db.host"), cfg.get<int>("db.port")};
}
};
auto appConfig = AppConfig::load(); // Load once
// Use appConfig.dbHost and appConfig.dbPortQ: Can I use config-cxx in production?
A: Yes! Config-cxx is actively used in production environments. It's stable, well-tested (high code coverage), and actively maintained.
Q: Is config-cxx thread-safe?
A: Yes, all operations are thread-safe. Multiple threads can safely access configuration simultaneously.
Q: What's the performance overhead?
A: Minimal. Configuration is loaded once at startup. Each get() call is an O(1) hash map lookup.
Q: Can I reload configuration at runtime?
A: Currently, configuration is loaded once at initialization. Runtime reloading is not supported yet (planned for future releases).
Q: Which file format should I use?
A: All formats (JSON, YAML, XML) work equally well. Choose based on your preference:
- JSON: Most common, good tooling support
- YAML: More readable, supports comments
- XML: Good for complex hierarchies
Q: Can I mix file formats?
A: No, all configuration files must use the same format. Choose one format for consistency.
Q: Where should I store secrets?
A: Never commit secrets to version control. Use:
- Environment variables (with
custom-environment-variables.json) - Local files (added to
.gitignore) - Secret management services (AWS Secrets Manager, HashiCorp Vault)
Q: Can I use both YAML and JSON files?
A: No, all config files in a directory must use the same format.
Q: How do I override a nested configuration value with environment variables?
A: Use custom-environment-variables.json to map:
{
"db": {
"host": "DB_HOST",
"credentials": {
"password": "DB_PASSWORD"
}
}
}Then: export DB_HOST=prod.example.com DB_PASSWORD=secret
Q: What happens if CXX_ENV is not set?
A: Defaults to "development". Files loaded: default.json + development.json
Q: Can I use custom environment names?
A: Yes! Any value works: export CXX_ENV=staging, export CXX_ENV=qa, etc.
Q: Do I need to install dependencies manually?
A: No. All dependencies (nlohmann/json, yaml-cpp, pugixml) are included as git submodules.
Q: Can I use config-cxx with CMake FetchContent?
A: Yes! See Installation Methods for FetchContent example.
Q: My project uses C++17. Can I use config-cxx?
A: No, config-cxx requires C++20. Consider upgrading or using an older version (if available).
Q: Does config-cxx work with vcpkg or Conan?
A: Not yet, but it's planned. Currently use git submodules or CMake FetchContent.
Q: Why is my configuration not loading?
A: Check:
CXX_ENVenvironment variable- Config files exist in
./configor$CXX_CONFIG_DIR - File names match:
default.json,{CXX_ENV}.json - File permissions are readable
Q: I get "Configuration key not found" error
A: The key doesn't exist in any loaded config file. Use has() to check first, or getOptional() for optional keys.
Q: Type conversion error when getting a value
A: The value type in config doesn't match requested type:
{"port": "3306"} // Stringconfig.get<int>("port"); // β Fails - is string, not intFix: Use correct type in config file: {"port": 3306}
Q: Build fails with "C++20 required" error
A: Ensure your compiler supports C++20 and CMakeLists.txt has:
set(CMAKE_CXX_STANDARD 20)Q: Can I programmatically set configuration values?
A: No, config-cxx is read-only by design. Configuration should come from files/environment.
Q: Can I validate configuration at startup?
A: Yes! See Common Patterns - Configuration Validation
Q: How do I handle optional configuration sections?
A: Use has() or getOptional():
if (config.has("redis.host")) {
// Redis is configured
setupRedis(config);
}Q: Can I use config-cxx in a library?
A: Yes, but consider accepting configuration as constructor parameters rather than reading directly, to make your library more flexible.
We welcome contributions! π
How to contribute:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for your changes
- Run all tests (
./config-cxx-UT) - Format code:
clang-format -i src/**/*.cpp include/**/*.h - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please read CONTRIBUTING.md for details
Need help? Join us on Discord π¬
This project is licensed under the MIT License - see the LICENSE file for details.
If you find config-cxx helpful, please:
- β Star the repository
- π Report bugs and issues
- π‘ Suggest new features
- π Improve documentation
- π Submit pull requests
- π¬ Discord Community
- π Issue Tracker
- π§ Email: Create an issue for support