| name | programming-cpp-policy-based-di |
| description | Use when injecting dependencies in C++ at compile time — hiding system calls, third-party APIs, or multiple dependencies behind swappable policy types; use when writing testable C++ without virtual dispatch overhead |
C++ Policy-Based Dependency Injection
Compile-time DI via template policy parameters. Zero runtime overhead, full mockability, no virtual functions.
Ownership rule: The class always owns its dependencies as value members constructed from the policy type.
Never store dependency references or accept deps via constructor — that mixes two different DI styles and creates dangling-reference risk.
Option 1 — Single Dependency (value member)
struct real_dependency {
return_type operation(args...) { return ::real_call(args...); }
};
template <typename Dependency = real_dependency>
struct my_class {
result_type do_work(args...) {
return dep_.operation(args...);
}
private:
Dependency dep_{};
};
Option 2 — Policy Bundle (multiple dependencies)
Group all dependency type aliases into one policy struct. The outer class owns each dep as a value member:
struct real_dep_a { return_type op_a(args...); };
struct real_dep_b { return_type op_b(args...); };
struct real_policy {
using DepA = real_dep_a;
using DepB = real_dep_b;
};
template <typename Policy = real_policy>
struct my_class {
void method_a(args...) { dep_a_.op_a(args...); }
void method_b(args...) { dep_b_.op_b(args...); }
private:
typename Policy::DepA dep_a_{};
typename Policy::DepB dep_b_{};
};
GMock Integration
GMock mock objects are non-copyable, so they cannot be owned by value inside the template class.
Use the 2-class + global approach: a thin wrapper struct delegates to a global mock pointer.
struct gmock_dependency {
MOCK_METHOD(return_type, operation, (args...));
};
std::unique_ptr<gmock_dependency> g_mock;
struct mock_dependency {
return_type operation(args...) { return g_mock->operation(args...); }
};
template <typename Dependency = real_dependency>
struct my_class {
result_type do_work(args...) { return dep_.operation(args...); }
private:
Dependency dep_{};
};
class my_class_test : public ::testing::Test {
protected:
void SetUp() override { g_mock = std::make_unique<gmock_dependency>(); }
void TearDown() override { g_mock.reset(); }
};
using sut = my_class<mock_dependency>;
TEST_F(my_class_test, does_work) {
EXPECT_CALL(*g_mock, operation(expected_args))
.WillOnce(testing::Return(expected_value));
sut obj;
EXPECT_EQ(obj.do_work(args...), expected_value);
}
Policy bundle variant — one global per dep, one wrapper per dep:
struct gmock_dep_a { MOCK_METHOD(return_type, op_a, (args...)); };
struct gmock_dep_b { MOCK_METHOD(return_type, op_b, (args...)); };
std::unique_ptr<gmock_dep_a> g_mock_a;
std::unique_ptr<gmock_dep_b> g_mock_b;
struct mock_dep_a { return_type op_a(args...) { return g_mock_a->op_a(args...); } };
struct mock_dep_b { return_type op_b(args...) { return g_mock_b->op_b(args...); } };
struct mock_policy {
using DepA = mock_dep_a;
using DepB = mock_dep_b;
};
class my_class_test : public ::testing::Test {
protected:
void SetUp() override {
g_mock_a = std::make_unique<gmock_dep_a>();
g_mock_b = std::make_unique<gmock_dep_b>();
}
void TearDown() override { g_mock_a.reset(); g_mock_b.reset(); }
};
using sut = my_class<mock_policy>;
TEST_F(my_class_test, method_a_calls_op_a) {
EXPECT_CALL(*g_mock_a, op_a(expected_args))
.WillOnce(testing::Return(expected_value));
sut obj;
obj.method_a(args...);
}
When to Use Each
| Scenario | Pattern |
|---|
| One external call to hide | Option 1 — single template param |
| 2+ related dependencies | Option 2 — policy bundle |
| GMock for any policy dep | 2-class + global (mocks are non-copyable) |
| Runtime polymorphism needed | Virtual interface instead |
| Hot path, zero overhead required | Either option (no vtable) |
MANDATORY: Always Use GMock for Tests
Never write hand-rolled spy structs. Every test for policy-based DI code must use MOCK_METHOD.
MOCK_METHOD satisfies any policy concept through duck-typing — no virtual base class required.
Why GMock, not hand-rolled spies:
EXPECT_CALL verifies exact arguments — spies require manual EXPECT_EQ per field
WillOnce(Return(...)) controls return values — spies hardcode them
- Argument matchers (
HasSubstr, _, Eq, AllOf) handle complex inputs
- Failure messages name the expectation — spy failures say "expected X got Y" with no context
InSequence verifies call order — spies cannot
Red flag: If you find yourself writing struct spy_X { std::vector<...> captured; ... } — stop. Use MOCK_METHOD instead.
Common Mistakes
| Mistake | Fix |
|---|
Storing dep as Dep& dep_ member | Dangling reference risk — store as value Dep dep_{} |
Accepting dep in constructor (my_class(Dep& d)) | Mixes constructor injection with template DI — pick one style |
Passing dep via shared_ptr/unique_ptr ctor arg | Same mixing problem — let the class construct its own dep |
| Hand-rolled spy structs in tests | Always use MOCK_METHOD |
| Wrapper calls itself recursively | Qualify the real call explicitly (e.g. ::free_function()) |
auto parameter in C++17 | Use std::string_view or explicit template<typename T> |
Missing typename on nested alias | typename Policy::DepA, not Policy::DepA |
Calling g_mock.op() on unique_ptr | Dereference first: g_mock->op() |
Forgetting override on SetUp/TearDown | Always add override; base declares them virtual |