cpp-idioms
SkillAI & modelsModern C++ (17/20/23) rewards RAII, value semantics, and zero-cost abstractions. Lean into the type system and smart pointers. Idiomatic C++ = safe, deterministic, performant.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the cpp-idioms skill
About this capability
Comprehensive sets of standards and practices designed to elevate the capabilities of AI coding agents.
What this skill tells your AI
The instructions your AI receives, as published by irahardianto/awesome-agv in .agents/skills/cpp-idioms/SKILL.md and read by ahel’s review.
C++ Idioms and Patterns
Modern C++ (17/20/23) rewards RAII, value semantics, and zero-cost abstractions. Lean into the type system and smart pointers. Idiomatic C++ = safe, deterministic, performant.
Scope: C++ coding idioms. Test naming: .agents/rules/testing-strategy.md.
Ownership and Memory
-
RAII — resources tied to object lifetime. No manual
new/delete. -
std::unique_ptrfor exclusive ownership,std::shared_ptronly when sharing is necessary. -
Move semantics — prefer moving over copying for expensive types.
-
Rule of Five/Zero:
// ✅ Rule of Zero — let compiler-generated defaults work class TaskService { std::unique_ptr<TaskStorage> storage_; std::shared_ptr<Logger> logger_; public: // No destructor, copy/move constructors needed — smart pointers handle it explicit TaskService(std::unique_ptr<TaskStorage> storage, std::shared_ptr<Logger> logger) : storage_(std::move(storage)), logger_(std::move(logger)) {} }; // ✅ Rule of Five — when managing raw resources (rare) class Buffer { char* data_; size_t size_; public: ~Buffer(); // Destructor Buffer(const Buffer&); // Copy constructor Buffer& operator=(const Buffer&); // Copy assignment Buffer(Buffer&&) noexcept; // Move constructor Buffer& operator=(Buffer&&) noexcept; // Move assignment }; -
Pass by value and move for sink parameters:
// ✅ Sink parameter — takes ownership void addTask(Task task) { tasks_.push_back(std::move(task)); } // ✅ Read-only — const reference void printTask(const Task& task); // ❌ Never pass smart pointers unless transferring ownership void processTask(std::shared_ptr<Task> task); // Unnecessary ref-count bump // ✅ If function doesn't need ownership void processTask(const Task& task);
Error Handling
For universal error handling principles, see
.agents/rules/error-handling-principles.md. Below: language-specific patterns only.
-
std::expected(C++23) or Result types for expected failures:// ✅ C++23 std::expected std::expected<Task, TaskError> getTask(std::string_view id) { auto it = tasks_.find(id); if (it == tasks_.end()) { return std::unexpected(TaskError::NotFound); } return it->second; } // Caller handles both paths auto result = getTask("123"); if (!result) { handleError(result.error()); return; } processTask(*result); -
Exceptions for exceptional conditions only. Never for control flow.
-
noexcepton move constructors and destructors. -
Domain error types:
enum class TaskError { NotFound, ValidationFailed, StorageUnavailable, }; // ✅ For exception-based paths class TaskNotFoundException : public std::runtime_error { std::string task_id_; public: explicit TaskNotFoundException(std::string id) : std::runtime_error("Task not found: " + id), task_id_(std::move(id)) {} const std::string& task_id() const noexcept { return task_id_; } };
Modern Features
-
std::string_viewfor read-only string parameters (no allocation):// ✅ Zero-copy string parameter void log(std::string_view message, std::string_view context); // ❌ Unnecessary copy void log(const std::string& message); -
constexprfor compile-time evaluation:constexpr int maxRetries = 3; constexpr auto timeout = std::chrono::seconds(30); constexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1); } -
Structured bindings:
auto [id, title] = getTask(); -
std::optionalfor nullable values:// ✅ Explicit nullability std::optional<Task> findById(std::string_view id); // Caller checks explicitly if (auto task = findById("123"); task.has_value()) { process(*task); } -
std::variantfor type-safe unions (sum types):using TaskResult = std::variant<Task, TaskError>; TaskResult result = getTask(id); std::visit(overloaded{ [](const Task& task) { process(task); }, [](TaskError err) { handleError(err); }, }, result); -
Concepts (C++20) for constrained templates:
template<typename T> concept Storable = requires(T t) { { t.id() } -> std::convertible_to<std::string_view>; { t.serialize() } -> std::same_as<std::vector<char>>; }; template<Storable T> void save(const T& entity); -
Ranges (C++20) for functional-style pipelines:
auto active_titles = tasks | std::views::filter([](const Task& t) { return t.is_active(); }) | std::views::transform(&Task::title) | std::views::take(10);
Interfaces and DI
// ✅ Interface (abstract class with pure virtuals)
class TaskStorage {
public:
virtual ~TaskStorage() = default;
virtual std::expected<Task, TaskError> getById(std::string_view id) = 0;
virtual void save(const Task& task) = 0;
};
// ✅ Production implementation
class PostgresTaskStorage : public TaskStorage {
// ...
};
// ✅ Test implementation
class MockTaskStorage : public TaskStorage {
// MOCK_METHOD from Google Mock, or hand-rolled
};
// ✅ Constructor injection
class TaskService {
std::unique_ptr<TaskStorage> storage_;
public:
explicit TaskService(std::unique_ptr<TaskStorage> storage)
: storage_(std::move(storage)) {}
};
Naming
- PascalCase for types. camelCase or snake_case for functions (project-consistent).
_suffix for private members:storage_,logger_.- No Hungarian notation.
- ALL_CAPS only for macros — never for constants (use
constexpr).
Anti-Patterns
- ❌ Raw
new/delete— usestd::make_unique/std::make_shared - ❌
using namespace std;in headers — pollutes global namespace - ❌ Catching
...without re-throwing — swallows all errors including crashes - ❌
const_castto remove constness — design flaw, fix the interface - ❌
reinterpret_castwithout safety justification — undefined behavior risk - ❌ C-style casts
(int)x— usestatic_cast<int>(x)for type safety - ❌
std::shared_ptrwhenstd::unique_ptrsuffices — unnecessary overhead - ❌ Copy-heavy loops without
const auto&:// ❌ Copies every Task for (auto task : tasks) { process(task); } // ✅ Reference — no copy for (const auto& task : tasks) { process(task); }
Testing
Google Test or Catch2. Google Mock for interface mocking.
// Google Test + Google Mock
TEST(TaskServiceTest, ReturnsNotFoundForMissingTask) {
auto storage = std::make_unique<MockTaskStorage>();
EXPECT_CALL(*storage, getById("999"))
.WillOnce(Return(std::unexpected(TaskError::NotFound)));
TaskService service(std::move(storage));
auto result = service.getTask("999");
EXPECT_FALSE(result.has_value());
EXPECT_EQ(result.error(), TaskError::NotFound);
}
Formatting and Static Analysis
| Tool | Purpose | Command |
|---|---|---|
clang-format | Formatting | clang-format -i src/**/*.cpp |
clang-tidy | Static analysis | clang-tidy src/*.cpp -- |
cppcheck | Bug detection | cppcheck --enable=all src/ |
| AddressSanitizer | Memory errors | -fsanitize=address |
| ThreadSanitizer | Race conditions | -fsanitize=thread |
| UBSanitizer | Undefined behavior | -fsanitize=undefined |
Related
- Code Idioms and Conventions .agents/rules/code-idioms-and-conventions.md
- Resources and Memory Management @.agents/rules/resources-and-memory-management-principles.md
- Concurrency and Threading Principles @.agents/rules/concurrency-and-threading-principles.md
- Error Handling Principles @.agents/rules/error-handling-principles.md
Signals
- GitHub stars
- 156
- Forks
- 53
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
cpp-idioms- Source
- github.com/irahardianto/awesome-agv