Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should safe pointers be used in strategy pattern?

Given a typical strategy pattern

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

class StrategyA : public Strategy
{
public:
    int execute() const override;
}

class StrategyB : public Strategy
{
public:
    int execute() const override;
}

I believe the 'pre-C++11' way to implement a context class would be something like

class ContextRaw
{
public:
    ContextRaw(Strategy* the_strategy);
    ~ContextRaw(); // Should this delete the_strategy_?
    int execute() const;
private:
    Strategy* the_strategy_;
}

To me, in this design it's not clear if Context should take responsibility for Strategy, and unless there is clear documentation stating otherwise, bad things might happen if it does

void trouble()
{
    StrategyA a_concrete_strategy;
    ContextRaw a_context(&a_concrete_strategy); // Oops, Context may try to delete stack variable
}

void more_trouble()
{
    Strategy* a_concrete_strategy = new StrategyA;
    ContextRaw* a_context       = new ContextRaw(a_concrete_strategy);
    ContextRaw* another_context = new ContextRaw(a_concrete_strategy);
    delete a_context;
    std::cout << another_context.execute() << std::endl; // Oops, the_strategy is deleted
}

In light of safe-pointers, should it now be preferable to inject a safe pointer, and have Context take ownership of the Strategy?

class ContextUnique
{
public:
    ContextUnique() = delete;
    ContextUnique(std::unique_ptr<Strategy> the_strategy);
    ~ContextUnique();
    int execute() const;
private:
    std::unique_ptr<Strategy> the_strategy_;
}

or if Strategy can be shared amongst different Context?

class ContextShared
{
public:
    ContextShared() = delete;
    ContextShared(std::shared_ptr<Strategy> the_strategy);
    ~ContextShared();
    int execute() const;
private:
    std::shared_ptr<Strategy> the_strategy_;
}

This design of course introduces problems of it's own, in particular only dynamically allocated Strategy's can be injected into Context.

like image 484
Daniel Avatar asked Feb 11 '23 00:02

Daniel


2 Answers

The design is up to the implementator.

Notice that in your examples you reference different ways of screwing up with Strategy pattern using non C++11 pointers.

To answer directly to your question :

Yes you should use smart pointers in strategy pattern.

To further answer the question :

You should use smart pointers whenever possible.

The reason is that smart pointers practically are self documenting in terms of memory ownership policy, so you get rid of some the drawbacks of "If no good documentation".

Considering the prototype that you expose for your Context class , you can tell users what are your expectations :

  • unique_ptr if you expect the user to pass memory ownership to you
  • shared_ptr if you expect the same strategy implementation to be used on multiple owners
  • weak_ptr if you want the user to handle memory management

What is safer, it's up to you. However , you are able to tell users that the Context can share it's concrete strategy with other Contexts OR that there is 1 Concrete strategy per Context.

As a design approach, I would suggest to go with 1 Strategy / Context (so unique_ptr) since your concrete strategies might end up having some internal variables that are unique / context , and things will get complicated from there on.

like image 135
MichaelCMS Avatar answered Feb 13 '23 14:02

MichaelCMS


You're doing it wrong.

In the light of std::function, everything you've just written is completely obsolete and you should just use std::function<int()> and some lambdas.

like image 39
Puppy Avatar answered Feb 13 '23 12:02

Puppy