3

My goal is to have a C++ Implementation of the Strategy Pattern without manual memory allocation. I do not feel like it ought to be necessary in the example that I give, conceptually speaking. Also, manual memory management is prone to errors. Keep in mind that my example is just an MWE, in reality all objects are more complicated and manual memory management often introduces bugs.


In the below example, the class Base needs to be given a Strategy to be constructed, which it will use to produce the output in its method getStrategyDecision(). Of course, it can not be given a Strategy because that class is abstract, so this argument has to be a reference. For my above reasons, it is not a pointer.

The following code does compile, but only because const has been added in several places.

class Strategy {
public:
    virtual int decision() const = 0;
};

class AlwaysZero : public Strategy {
public:
    int decision() const { return 0; }
};


class Base {
private:
    Strategy const &strategy;
public:
    Base(Strategy const &s) : strategy(s) {}

    int getStrategyDecision() {
        return strategy.decision();
    }
};


class Main : public Base {
public:
    Main() : Base(AlwaysZero()) {}
};


int main ()
{
    Main invoker;
    return invoker.getStrategyDecision();
}

If you remove the const qualifiers, it fails because in the constructor of Main, passing the temporary object AlwaysZero() to the reference argument of the constructor of Base is illegal.

I could understand that error, but

  1. Why does it work when everything is const? This seems to suggest it should work without const equally well and I am just missing something.
  2. I do not even want a "temporary" object, the only reason I have to do this is because I cannot pass a Strategy itself, because the class is abstract.

Again, I know I could solve this by making the variable strategy a pointer to a Strategy and passing a new AlwaysZero() to the Base constructor. This is what I do not want, as explained above.


My question is twofold. First, I want to know why my example compiles, when it does not without const. I want to understand what is going on here.

Secondly, I would like to know if this example can be modified so that strategy is no longer const (and it works), but without using any kind of pointer. Only if the answer to this second question is "no", I will start looking for clever ways to avoid the problem (such as smart pointers, as suggested in the comments).

PS: I use g++ as a compiler. It might or might not matter.

Jesko Hüttenhain
  • 1,278
  • 10
  • 28
  • Why not use smart pointers such as unique_ptr? More or less everything you are worried about regarding "Manual memory management" will go away but you will keep pointer semantics and behaviours. – const_ref May 04 '17 at 09:00
  • @const_ref: That is a very reasonable suggestion, thanks. However, I would only use it if the answer to the question is *"no"*, i.e. it is strictly impossible to do this without pointers. I am also quite curious as to why this example works with `const` and not without it, i.e. I really want to understand what is going on here. – Jesko Hüttenhain May 04 '17 at 09:05
  • Fair enough, might want to update your question to say so. Smart Pointers arent what I would class as manual memory management :) – const_ref May 04 '17 at 09:07
  • @const_ref: Good point, done. – Jesko Hüttenhain May 04 '17 at 09:20
  • 1
    A const reference prolongs the life of a temporary. See http://stackoverflow.com/q/2784262/3422652 and https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/. The same is not true of a non-const reference. – Chris Drew May 04 '17 at 09:40
  • Just to be clear, you can use pointers without memory management. – Chris Drew May 04 '17 at 09:46
  • @ChrisDrew: Okay, but why? I read through both the answer and the linked article, they simply restate what I already observed. **Why** can't C++ prolong the lifetime for non-`const` references, or rather, why was it decided in the standard that it does not? Also, if you have a good suggestion on how to do this with pointers but without calling `new`, by all means, please post it. – Jesko Hüttenhain May 04 '17 at 09:52

1 Answers1

3

The reason your code compiles is that a const reference prolongs the life of a temporary but a non-const reference does not. See https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ for a detailed explanation.

As for why the C++ standard says a non-const reference doesn't prolong the life of a temporary. As explained here, non-const references can't be bound to temporaries in the first place. See here for a simple example of why.

So, your technique should work fine. But if you want some other options...

Strategy Template

template<typename T>
class Base {
private:
    T strategy;
public:
    Base(const T& s) : strategy(s) {}

    int getStrategyDecision() {
        return strategy.decision();
    }
};

class Main : public Base<AlwaysZero> {
public:
    Main() : Base(AlwaysZero()) {}
};

Non-owning Pointer

You can use pointers without manual memory management. As long as they are non-owning pointers.

class Base {
private:
    Strategy *strategy;
protected:
    void setStrategy(Strategy& s) { strategy = &s; }
public:
    int getStrategyDecision() {
        return strategy->decision();
    }
};

class Main : public Base {
    AlwaysZero my_strategy;
public:
    Main()  { setStrategy(my_strategy); }
};
Community
  • 1
  • 1
Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • The template version is a reasonable workaround, but I still do not understand why `const` references can prolong the lifetime of an object but others cannot. I simply do not understand the deeper reason for this. For your second suggestion, the problem for me here is that the code no longer guarantees that `Base` always has a functional strategy attached to it. The pointer could be `NULL`, in your particular version any child of `Base` might simply decide to not call `setStrategy` at all. – Jesko Hüttenhain May 04 '17 at 10:04
  • For strategies that does not hold its state it might be simpler to just call static: T::decision() – Maciej May 04 '17 at 10:25
  • @Maciej Sure, there are number simplifications that could be made in this case but as OP said their real situation is much more complicated I avoided making them. – Chris Drew May 04 '17 at 10:56
  • @JeskoHüttenhain I've added some links that try to explain *why* const references can't prolong the lifetime of a temporary object. – Chris Drew May 04 '17 at 10:59
  • Alright! I think I got it now. I can't say I am happy with it, but I understand much better now. Thanks for your patience! +1 and Accept. – Jesko Hüttenhain May 04 '17 at 16:54