AlwaysLearning
AlwaysLearning

Reputation: 8011

Decoupling host and policy classes at the cost of stateful policy classes and not following Item 26 of Effective C++

This post consists of describing a problem with the straightforward implementation of the policy-based design, proposing an alternative implementation, analyzing the proposed implementation and asking help in giving correct weight to the different factors in the analysis. I apologize for the length of the post and hope that you will stick with me.

PROBLEM DESCRIPTION

Suppose that we use policy-based design as follows:

template <typename FooPolicy> 
struct Alg {
    void operator()() {
        ...
        FooPolicy::foo(arguments);
        ...
    }
};

There is a degree of coupling between the above host class and the policy: if the signature of FooPolicy::foo changes, then the code in Alg::operator() has to change accordingly.

The coupling becomes much tighter if policy classes are given a degree of freedom for choosing an interface. For example, suppose that FooPolicy may implement either foo without parameters or foo with one integer parameter (the implementation for this case was suggested here):

template <typename FooPolicy> struct Alg {
    void operator()() {
        int arg = 5; // any computation can be here
        // using tag dispatch to call the correct `foo`
        foo(std::integral_constant<bool, FooPolicy::paramFlag>{}, arg);
    }

private:
    void foo(std::true_type, int param) { FooPolicy::foo(param); }
    void foo(std::false_type, int param) {
        (void)param;
        FooPolicy::foo();
    }
};

struct SimpleFoo {
    static constexpr bool paramFlag = false;
    static void foo();
};

struct ParamFoo {
    static constexpr bool paramFlag = true;
    static void foo(int param);
};

Obviously, whenever one adds a policy class with a different interface, he will have to update the dispatching mechanism, which can become complicated.

PROPOSED DESIGN

I am considering a design, whereby the algorithm would provide an interface that function members of the policy class can use to get data instead of accepting arguments. For the above example, it might look as follows:

// The policy-independent part of Alg factored out
struct AlgPolicyIndependent {
    int getArg() const { return arg; }
protected:
    int arg;
};

// The interface to be used by FooPolicy
struct FooPolicyServices {
    FooPolicyServices(const AlgPolicyIndependent &myAlg) : alg(myAlg) {};
    int getArg() const { return alg.getArg(); }
private:
    const AlgPolicyIndependent &alg;
};

template <typename FooPolicy>
struct Alg : private AlgPolicyIndependent {
    Alg() : fooPolicy(FooPolicyServices(*this)) {};
    void operator()() {
        arg = 5; // any computation can be here
        fooPolicy.foo();
    }
private:
    FooPolicy fooPolicy;
};

struct SimpleFoo {
    SimpleFoo(const FooPolicyServices &myS) : s(myS) {};
    void foo() { std::cout << "In SimpleFoo" << std::endl; }
private:
    const FooPolicyServices &s;
};

struct ParamFoo {
    ParamFoo(const FooPolicyServices &myS) : s(myS) {};
    void foo() { std::cout << "In ParamFoo " << s.getArg() << std::endl; }
private:
    const FooPolicyServices &s;
};

ANALYSIS

With this design, a policy is free to use any data obtainable by using the public interface of the corresponding Services class. In our example, ParamFoo::foo got the algorithm's arg by using FooPolicyServices::getArg. The host class simply calls FooPolicy::foo without arguments and this will not have to change even if FooPolicy::foo changes, which is the decoupling we wanted.

I see two disadvantages to this design:

  1. arg has become part of the state of Alg instead of being a local variable in Alg::operator(), which goes against Item 26 of Effective C++ saying that variables should be defined as late as possible. However, the reasoning of that item does not apply if the cost of the extra initialization of arg is negligible compared to the cost of running the algorithm.

  2. Policy classes have gotten a state. So, we cannot use policies by simply calling their static member functions.

QUESTIONS

Three questions:

  1. Is the decoupling achieved by the proposed design worth the two disadvantages listed above?

  2. Are there disadvantages that I overlooked?

  3. Does the proposed design have a name?


Based on the reply by @Useless, here is an updated implementation. This implementation makes it possible to have stateless policy classes, but has an overhead of passing the same reference to a Services object each time a policy is used.

// The policy-independent part of Alg
struct AlgPolicyIndependent {
    int getArg() const { return arg; }
protected:
    int arg;
};

// The interface to be used by FooPolicy
struct FooPolicyServices {
    FooPolicyServices(const AlgPolicyIndependent &myAlg) : alg(myAlg) {};
    int getArg() const { return alg.getArg(); }
private:
    const AlgPolicyIndependent &alg;
};

template <typename FooPolicy>
struct Alg : private AlgPolicyIndependent {
    Alg() : fooPolicyServices(*this) {};
    void operator()() {
        arg = 5; // any computation can be here
        FooPolicy::foo(fooPolicyServices);
    }
private:
    FooPolicyServices fooPolicyServices;
};

struct SimpleFoo {
    static void foo(const FooPolicyServices &s) {
        (void)s;
        std::cout << "In SimpleFoo" << std::endl;
    }
};

struct ParamFoo {
    static void foo(const FooPolicyServices &s) {
        std::cout << "In ParamFoo " << s.getArg() << std::endl;
    }
};

Upvotes: 0

Views: 204

Answers (1)

Useless
Useless

Reputation: 67743

There is a degree of coupling between the above host class and the policy

Or, "Is the coupling inherent in the Policy pattern a problem"?

No. There is an equivalent degree of coupling between each of the algo and the policy, and the interface, which one requires and the other implements.

Consider the runtime-polymorphic equivalent, the Strategy:

struct IStrategy {
    virtual ~IStrategy() {}
    virtual void foo() = 0;
};
struct FooStrategy: public IStrategy {
    void foo() override;
}
void algo(IStrategy *s) {
    // ...
    s->foo();
}

Now, there is exactly the same degree of coupling between the concrete strategy and the algorithm function on the (explicit base-class) interface in the Strategy pattern, as there is between the policy and algorithm template on the (implicit duck-typed) interface in the Policy pattern.

I'm not suggesting this coupling doesn't exist, but I am pointing out that this degree of coupling is not usually considered excessive.

Note there's an exact run-time analogy for your variation too - optionally implementing other (and more specific) interfaces, and using dynamic_cast to investigate at run-time which are supported.


Questions

  1. Is the decoupling ... worth the two disadvantages listed above?

    Maybe! It depends how troublesome the coupling has become in practice, and how much state is being passed around.

    In any case ...

  2. Are there disadvantages that I overlooked?

    There are plenty of disadvantages to statefulness in general, and your suggested design in particular:

    • global (ie, non-local-scope) state interacts badly with concurrency (use the same algorithm object from multiple threads and everything breaks)
    • it interacts badly with re-entrant use (accidentally use the same algorithm object from inside its own Policy call, and everything breaks)
    • if you want to re-use a stateful object, you may need to provide an additional method to reset its state
    • you then have to remember to call that method everywhere the object could be reused
    • the two points above are essentially a bad, manual, error-prone emulation of just using local-scope state in the first place
    • in addition to writing that extra code, you also made your object bigger, to store state you don't really want outside the method scope anyway
    • in your particular case, you also made the policy bigger (to store a pointer back to your state) and every policy call now has an extra indirection in the pointer chase this->services->value to get an argument

    While these are problems, they're also completely avoidable here. Just pass a local-scope context object to the policy method on every call. Then:

    • there's no requirement for the algo object to be stateful at all, because the context object has local scope in the method
    • there's no need for the policy to be stateful either, because the context can simply be passed as the only argument on each call

    note that in your edit, the algorithm is still stateful. However, the services/independent things (what I called the context) can just be a local variable in the algorithm method. There's no need to preserve it outside that method, and replacing the data member with a local makes the algo stateless.

  3. Does the proposed design have a name?

    It looks like the Context pattern to me. The original suggestion looks like a sort of uncomfortable home-rolled dependency injection, but I'd stick with stateless policy & algorithm, and pass the working state around in a Context object for preference.

Upvotes: 1

Related Questions