agentleFS
ROCmROCm/rocprofiler-systems-skills/skills/programming-cpp-policy-based-di/SKILL.md

programming-cpp-policy-based-di

Skill5 starsChanged 16 days agoMITView on GitHub

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

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Never store dependency references or accept deps via constructor — that mixes two different DI styles and creates dangling-reference risk.

    line 11
  2. Distinctive:

    Use the 2-class + global approach: a thin wrapper struct delegates to a global mock pointer.

    line 56
  3. Very specific:

    Never write hand-rolled spy structs: Every test for policy-based DI code must use MOCK_METHOD.

    line 140
  4. Very specific:

    Red flag: If you find yourself writing struct spy_X { std::vector<...> captured.

    line 151

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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

ScenarioPattern
One external call to hideOption 1 — single template param
2+ related dependenciesOption 2 — policy bundle
GMock for any policy dep2-class + global (mocks are non-copyable)
Runtime polymorphism neededVirtual interface instead
Hot path, zero overhead requiredEither 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

MistakeFix
Storing dep as Dep& dep_ memberDangling 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 argSame mixing problem — let the class construct its own dep
Hand-rolled spy structs in testsAlways use MOCK_METHOD
Wrapper calls itself recursivelyQualify the real call explicitly (e.g. ::free_function())
auto parameter in C++17Use std::string_view or explicit template<typename T>
Missing typename on nested aliastypename Policy::DepA, not Policy::DepA
Calling g_mock.op() on unique_ptrDereference first: g_mock->op()
Forgetting override on SetUp/TearDownAlways add override; base declares them virtual

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.