| name | lang-cpp-library-dev |
| description | C++-specific library development patterns. Use when creating C++ libraries, designing header-only vs compiled libraries, configuring CMake for library targets, managing ABI stability and versioning, integrating with Conan/vcpkg, documenting with Doxygen, or testing with GoogleTest/Catch2. Extends meta-library-dev with C++ tooling and idioms. |
C++ Library Development
C++-specific patterns for library development. This skill extends meta-library-dev with C++ tooling, modern C++ idioms (C++17/20/23), and ecosystem practices.
This Skill Extends
meta-library-dev - Foundational library patterns (API design, versioning, testing strategies)
lang-cpp-dev - Core C++ language features and patterns
For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first.
This Skill Adds
- Library architecture: Header-only vs compiled libraries, template libraries
- CMake tooling: Library targets, installation, package configuration
- ABI stability: Version management, symbol visibility, compatibility
- Package managers: Conan, vcpkg integration
- Documentation: Doxygen patterns for libraries
- Testing: GoogleTest, Catch2 patterns for library testing
This Skill Does NOT Cover
- General library patterns - see
meta-library-dev
- Core C++ syntax and features - see
lang-cpp-dev
- CMake basics - see
lang-cpp-cmake-dev
- Memory optimization - see
lang-cpp-memory-eng
- Performance profiling - see
lang-cpp-profiling-eng
Quick Reference
| Task | Command/Pattern |
|---|
| CMake library target | add_library(mylib src/mylib.cpp) |
| Header-only library | add_library(mylib INTERFACE) |
| Build library | cmake --build build |
| Install library | cmake --install build |
| Run tests | ctest --test-dir build |
| Generate docs | doxygen Doxyfile |
| Conan install | conan install . --build=missing |
| vcpkg install | vcpkg install mylib |
Library Architecture
Header-Only vs Compiled Libraries
Header-Only Libraries:
#ifndef MY_LIBRARY_HPP
#define MY_LIBRARY_HPP
#include <string>
#include <vector>
namespace mylib {
template<typename T>
class Container {
public:
void add(const T& item) {
items_.push_back(item);
}
size_t size() const {
return items_.size();
}
private:
std::vector<T> items_;
};
inline std::string version() {
return "1.0.0";
}
}
#endif
Compiled Libraries:
#ifndef MYLIB_HPP
#define MYLIB_HPP
#include <string>
#include <memory>
namespace mylib {
class Parser {
public:
Parser();
~Parser();
bool parse(const std::string& input);
std::string result() const;
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
}
#endif
#include "mylib/mylib.hpp"
namespace mylib {
struct Parser::Impl {
std::string result;
};
Parser::Parser() : impl_(std::make_unique<Impl>()) {}
Parser::~Parser() = default;
bool Parser::parse(const std::string& input) {
impl_->result = + input;
;
}
{
impl_->result;
}
}
When to Use Each
| Pattern | Use When | Pros | Cons |
|---|
| Header-only | Templates, small utilities | No linking, easy distribution | Longer compile times, code bloat |
| Compiled | Large implementations, ABI stability | Fast compilation, smaller binaries | Requires linking, distribution complexity |
| Hybrid | Mix of both | Best of both worlds | More complex build setup |
CMake Library Configuration
Basic Library Target
cmake_minimum_required(VERSION 3.15)
project(MyLibrary 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)
# Compiled library
add_library(mylib
src/parser.cpp
src/serializer.cpp
)
# Include directories
target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
# Link dependencies
target_link_libraries(mylib
PUBLIC
fmt::fmt
PRIVATE
nlohmann_json::nlohmann_json
)
# Set library properties
set_target_properties(mylib PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
PUBLIC_HEADER include/mylib/mylib.hpp
)
Header-Only Library
# Interface library (header-only)
add_library(mylib INTERFACE)
target_include_directories(mylib
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_compile_features(mylib INTERFACE cxx_std_17)
# Header-only dependencies
target_link_libraries(mylib
INTERFACE
range-v3::range-v3
)
Static vs Shared Library
# Option for library type
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
add_library(mylib
src/parser.cpp
src/serializer.cpp
)
# Or explicitly
add_library(mylib_static STATIC src/parser.cpp)
add_library(mylib_shared SHARED src/parser.cpp)
# Symbol visibility for shared libraries
include(GenerateExportHeader)
generate_export_header(mylib
EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/include/mylib/export.hpp
)
target_compile_definitions(mylib
PRIVATE
MYLIB_EXPORTS # When building the library
)
Installation Configuration
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
# Install targets
install(TARGETS mylib
EXPORT mylibTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mylib
)
# Install headers
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
# Export targets
install(EXPORT mylibTargets
FILE mylibTargets.cmake
NAMESPACE mylib::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib
)
# Generate package config
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/mylibConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/mylibConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib
)
# Generate version file
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/mylibConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
# Install package config files
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mylibConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/mylibConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib
)
Package Config Template
# cmake/mylibConfig.cmake.in
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# Find dependencies
find_dependency(fmt)
# Include targets
include("${CMAKE_CURRENT_LIST_DIR}/mylibTargets.cmake")
check_required_components(mylib)
Symbol Visibility and ABI Stability
Export Macros
#ifndef MYLIB_EXPORT_HPP
#define MYLIB_EXPORT_HPP
#ifdef _WIN32
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
#else
#define MYLIB_API __attribute__((visibility("default")))
#endif
#endif
#include "mylib/export.hpp"
namespace mylib {
class MYLIB_API Parser {
public:
Parser();
~Parser();
bool parse(const std::string& input);
};
}
CMake Visibility Configuration
# Set default symbol visibility to hidden
set_target_properties(mylib PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)
# Generate export header
include(GenerateExportHeader)
generate_export_header(mylib
BASE_NAME MYLIB
EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/include/mylib/export.hpp
)
ABI Versioning
namespace mylib {
inline namespace v1 {
class Parser {
};
}
}
mylib::Parser p;
PIMPL Pattern for ABI Stability
#ifndef MYLIB_DATABASE_HPP
#define MYLIB_DATABASE_HPP
#include <memory>
#include <string>
namespace mylib {
class Database {
public:
Database(const std::string& path);
~Database();
Database(const Database&);
Database& operator=(const Database&);
Database(Database&&) noexcept;
Database& operator=(Database&&) noexcept;
void execute(const std::string& query);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
}
#endif
#include "mylib/database.hpp"
#include <vector>
#include <map>
namespace mylib {
struct Database::Impl {
std::string path;
std::vector<std::string> cache;
std::map<std::string, > indices;
};
Database::( std::string& path)
: (std::<Impl>()) {
impl_->path = path;
}
Database::~() = ;
Database::( Database& other)
: (std::<Impl>(*other.impl_)) {}
Database& Database::=( Database& other) {
( != &other) {
impl_ = std::<Impl>(*other.impl_);
}
*;
}
Database::(Database&&) = ;
Database& Database::=(Database&&) = ;
{
}
}
Template Library Patterns
Header Organization
include/
└── mylib/
├── mylib.hpp # Main public header
├── container.hpp # Template class
└── detail/
└── impl.hpp # Implementation details
// include/mylib/container.hpp
#ifndef MYLIB_CONTAINER_HPP
#define MYLIB_CONTAINER_HPP
#include <vector>
#include <algorithm>
namespace mylib {
template<typename T>
class Container {
public:
void add(const T& item);
void add(T&& item);
template<typename Predicate>
void remove_if(Predicate pred);
size_t size() const { return items_.size(); }
private:
std::vector<T> items_;
};
// Template implementations in same header
template<typename T>
void Container<T>::add(const T& item) {
items_.push_back(item);
}
template<typename T>
void Container<T>::add(T&& item) {
items_.push_back(std::move(item));
}
template<typename T>
template<typename Predicate>
void Container<T>::remove_if(Predicate pred) {
items_.erase(
std::remove_if(items_.begin(), items_.end(), pred),
items_.end()
);
}
} // namespace mylib
#endif
Extern Templates (Reduce Compile Time)
template<typename T>
class Container {
void add(const T& item);
};
extern template class Container<int>;
extern template class Container<std::string>;
#include "mylib.hpp"
template class Container<int>;
template class Container<std::string>;
Concepts for Template Constraints (C++20)
#include <concepts>
namespace mylib {
template<typename T>
concept Serializable = requires(T t) {
{ t.serialize() } -> std::convertible_to<std::string>;
};
template<typename T>
concept Numeric = std::is_arithmetic_v<T>;
template<Serializable T>
class DataStore {
public:
void save(const T& data) {
std::string serialized = data.serialize();
}
};
template<typename T>
concept Container = requires(T t) {
typename T::value_type;
{ t.size() } -> std::convertible_to<size_t>;
{ t.begin() } -> std::input_iterator;
{ t.end() } -> std::input_iterator;
};
template<Container C>
void process(const C& container) {
for (const auto& item : container) {
}
}
}
Package Manager Integration
Conan Configuration
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
class MyLibConan(ConanFile):
name = "mylib"
version = "1.0.0"
license = "MIT"
author = "Your Name <your.email@example.com>"
url = "https://github.com/username/mylib"
description = "A C++ library for parsing"
topics = ("parsing", "cpp", "library")
settings = "os", "compiler", "build_type", "arch"
options = {
"shared": [True, False],
"fPIC": [True, False]
}
default_options = {
"shared": False,
"fPIC": True
}
exports_sources = "CMakeLists.txt", "src/*", "include/*"
def requirements(self):
self.requires("fmt/9.1.0")
self.requires("nlohmann_json/3.11.2")
def config_options(self):
if self.settings.os == "Windows":
.options.fPIC
():
cmake_layout()
():
tc = CMakeToolchain()
tc.generate()
():
cmake = CMake()
cmake.configure()
cmake.build()
():
cmake = CMake()
cmake.install()
():
.cpp_info.libs = []
.cpp_info.includedirs = []
vcpkg Configuration
{
"name": "mylib",
"version": "1.0.0",
"description": "A C++ library for parsing",
"homepage": "https://github.com/username/mylib",
"license": "MIT",
"dependencies": [
"fmt",
"nlohmann-json"
],
"features": {
"tests": {
"description": "Build tests",
"dependencies": [
"gtest"
]
}
}
}
# ports/mylib/portfile.cmake
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO username/mylib
REF v1.0.0
SHA512 <hash>
HEAD_REF main
)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
OPTIONS
-DBUILD_TESTING=OFF
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/mylib)
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)
Documentation with Doxygen
Doxyfile Configuration
# Doxyfile
PROJECT_NAME = "MyLib"
PROJECT_BRIEF = "A modern C++ parsing library"
PROJECT_VERSION = 1.0.0
INPUT = include/ src/ README.md
FILE_PATTERNS = *.hpp *.cpp *.md
RECURSIVE = YES
EXCLUDE_PATTERNS = */detail/* */test/*
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
GENERATE_HTML = YES
HTML_OUTPUT = docs/html
GENERATE_LATEX = NO
USE_MDFILE_AS_MAINPAGE = README.md
MARKDOWN_SUPPORT = YES
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = NO
HAVE_DOT = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
CLASS_DIAGRAMS = YES
Documentation Comments
#ifndef MYLIB_PARSER_HPP
#define MYLIB_PARSER_HPP
namespace mylib {
class Parser {
public:
explicit Parser(bool strict = false);
bool parse(const std::string& input);
const std::string& result() const;
private:
bool strict_;
std::string result_;
};
;
}
CMake Integration
# Find Doxygen
find_package(Doxygen)
if(DOXYGEN_FOUND)
# Configure Doxyfile
set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in)
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
# Add documentation target
add_custom_target(docs
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating API documentation with Doxygen"
VERBATIM
)
endif()
Testing with GoogleTest
CMake Test Configuration
# Enable testing
enable_testing()
# Find GoogleTest
find_package(GTest REQUIRED)
# Test executable
add_executable(mylib_test
tests/parser_test.cpp
tests/serializer_test.cpp
)
target_link_libraries(mylib_test
PRIVATE
mylib
GTest::gtest
GTest::gtest_main
)
# Discover tests
include(GoogleTest)
gtest_discover_tests(mylib_test)
Test Structure
#include <gtest/gtest.h>
#include "mylib/parser.hpp"
namespace mylib {
namespace test {
class ParserTest : public ::testing::Test {
protected:
void SetUp() override {
parser = std::make_unique<Parser>();
}
void TearDown() override {
parser.reset();
}
std::unique_ptr<Parser> parser;
};
TEST_F(ParserTest, ParseValidInput) {
ASSERT_TRUE(parser->parse("valid input"));
EXPECT_EQ(parser->result(), "parsed: valid input");
}
TEST_F(ParserTest, ParseEmptyInput) {
EXPECT_FALSE(parser->parse(""));
}
TEST_F(ParserTest, ParseWithStrictMode) {
Parser strict_parser(true);
EXPECT_THROW(strict_parser.parse("invalid"), std::runtime_error);
}
class ParserParamTest : public ::testing::TestWithParam<std::string> {};
TEST_P(ParserParamTest, ParseMultipleInputs) {
Parser p;
(p.(()));
}
(
ValidInputs,
ParserParamTest,
::testing::(, , )
);
}
}
Testing with Catch2
CMake Configuration
find_package(Catch2 3 REQUIRED)
add_executable(mylib_test
tests/parser_test.cpp
)
target_link_libraries(mylib_test
PRIVATE
mylib
Catch2::Catch2WithMain
)
include(Catch)
catch_discover_tests(mylib_test)
Test Structure
#include <catch2/catch_test_macros.hpp>
#include "mylib/parser.hpp"
TEST_CASE("Parser handles valid input", "[parser]") {
mylib::Parser p;
SECTION("Basic parsing") {
REQUIRE(p.parse("test input"));
REQUIRE(p.result() == "parsed: test input");
}
SECTION("Empty input fails") {
REQUIRE_FALSE(p.parse(""));
}
}
TEST_CASE("Parser with strict mode", "[parser][strict]") {
mylib::Parser p(true);
REQUIRE_THROWS_AS(p.parse("invalid"), std::runtime_error);
}
TEMPLATE_TEST_CASE("Container works with different types",
"[container][template]",
int, double, std::string) {
mylib::Container<TestType> container;
TestType value{};
container.add(value);
REQUIRE(container.size() == 1);
}
Versioning Best Practices
Semantic Versioning
MAJOR.MINOR.PATCH (e.g., 1.2.3)
MAJOR: Incompatible API changes (breaking changes)
MINOR: Backward-compatible functionality additions
PATCH: Backward-compatible bug fixes
Version Header
#ifndef MYLIB_VERSION_HPP
#define MYLIB_VERSION_HPP
#define MYLIB_VERSION_MAJOR 1
#define MYLIB_VERSION_MINOR 0
#define MYLIB_VERSION_PATCH 0
#define MYLIB_VERSION \
(MYLIB_VERSION_MAJOR * 10000 + \
MYLIB_VERSION_MINOR * 100 + \
MYLIB_VERSION_PATCH)
#define MYLIB_VERSION_STRING "1.0.0"
namespace mylib {
struct Version {
static constexpr int major = MYLIB_VERSION_MAJOR;
static constexpr int minor = MYLIB_VERSION_MINOR;
static constexpr int patch = MYLIB_VERSION_PATCH;
static constexpr const char* string = MYLIB_VERSION_STRING;
};
}
#endif
CMake Version Configuration
# CMakeLists.txt
project(MyLib VERSION 1.0.0)
# Generate version header
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/include/mylib/version.hpp.in
${CMAKE_CURRENT_BINARY_DIR}/include/mylib/version.hpp
)
#ifndef MYLIB_VERSION_HPP
#define MYLIB_VERSION_HPP
#define MYLIB_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define MYLIB_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define MYLIB_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define MYLIB_VERSION_STRING "@PROJECT_VERSION@"
#endif
Example: Complete Library Structure
mylib/
├── CMakeLists.txt
├── conanfile.py
├── vcpkg.json
├── README.md
├── LICENSE
├── CHANGELOG.md
├── Doxyfile
├── .clang-format
├── include/
│ └── mylib/
│ ├── mylib.hpp # Main public header
│ ├── parser.hpp
│ ├── serializer.hpp
│ ├── version.hpp
│ └── export.hpp
├── src/
│ ├── parser.cpp
│ └── serializer.cpp
├── tests/
│ ├── CMakeLists.txt
│ ├── parser_test.cpp
│ └── serializer_test.cpp
├── examples/
│ ├── CMakeLists.txt
│ ├── basic_usage.cpp
│ └── advanced_usage.cpp
├── docs/
│ └── Doxyfile.in
└── cmake/
├── mylibConfig.cmake.in
└── FindMyLibDeps.cmake
Anti-Patterns
1. Exposing Implementation Details
class Parser {
public:
std::shared_ptr<InternalNode> parse(const std::string& input);
};
class Parser {
public:
Document parse(const std::string& input);
};
2. Using using namespace in Headers
using namespace std;
class Parser {
string parse(const string& input);
};
class Parser {
std::string parse(const std::string& input);
};
3. Inline Everything
class Parser {
void parse(const std::string& input) {
}
};
class Parser {
void parse(const std::string& input);
};
4. Breaking ABI Without Major Version Bump
class Data {
int value;
};
class Data {
int value;
int newValue;
};
References