3

There is, more often than not, a need to share same data between different objects/classes. I am aware of a few different ways of doing this:

  1. Global variables: Loathed by most and for the right reasons.
  2. Singletons: This provides limited control on who can or cannot modify the data, at least for the problem discussed below.
  3. Dependency Injection: Same problem as singletons.

Sometimes, this data sharing gives rise to design with weak encapsulation of this shared data. Two common situations, that arise quite often, are as follows:

States implementing a state interface in a state machine need access to the same set of data.

class SharedData
{
public:
double GetVar() const {return var;}
bool GetFlag() const {return flag;}
void SetVar(double in_var) {var = in_var;}
void SetFlag(bool in_flag) {flag = in_flag;}

private:
double var;
bool flag;
};

class StateIface
{
public:
virtual void Run(SharedData* in_shared_data) = 0;
};

class ConcreteStateA : public StateIface
{
virtual void Run(SharedData* in_shared_data) final;
};

class ConcreteStateB : public StateIface
{
virtual void Run(SharedData* in_shared_data) final;
};

Here, concrete implementations such as ConcreteStateA will need access to SharedData to e.g. get/set certain data, may be use this information to decide on state transition etc. As in the example above, we could declare SharedData as a class and provide accessors/mutators. Or we could simply declare SharedData as a struct. However, in both cases, the concrete implementations will be able to modify any parameter within SharedData. For example, let's say that ConcreteStateA has nothing to do with flag and hence should not be able to mutate it. With the given interface, however, we cannot control that behavior. Both ConcreteStateA and ConcreteStateB have access to all the data and can get/set any parameter. Is there a better design/solution for this problem? One that offers more protection to the shared data. Or, can we somehow enforce a constraint that a certain ConcreteState is able to modify only certain parameters of the SharedData while still implementing the common StateInterface?

Sub-routines of a giant algorithm implementing a sub-routine interface need access to the same set of data.

class SubroutineInterface
{
public:
virutal void DoSubroutine(SharedData* in_shared_data) = 0;
}

class ConcreteSubroutine : public SubroutineInterface
{
public:
virutal void DoSubroutine(SharedData* in_shared_data) final;
};

Same questions as in the state machine example...

TheFlyingObject
  • 181
  • 1
  • 7
  • components with static data members? – Swift - Friday Pie May 21 '18 at 13:36
  • If interface gives access to whole sharedData, concrete derived class would also has access... If you don't want to allow modification, don't provide setter, or provide const object. – Jarod42 May 21 '18 at 13:36
  • @Swift No, don't have to be static. – TheFlyingObject May 21 '18 at 13:45
  • @Jarod42 Yes, that is why I asked the question. const would mean concrete objects could not set stuff which they might need to (until you declare everything mutable), which adds little value in this case. – TheFlyingObject May 21 '18 at 13:48
  • Unclear for me what you want to protect. if `StateIface` and so `ConcreteState` should have both read/write access to `var`, provide getter/setter or similar, if they should not modify `flag`, don't provide `SetFlag`. – Jarod42 May 21 '18 at 14:03
  • @Jarod42 I think I have not been able to clearly explain the problem. And I have edited the question to do so. Please have a look. – TheFlyingObject May 21 '18 at 15:44
  • @TheFlyingObject Before I go and write a second answer, would something like this match your needs? https://godbolt.org/g/JvmAQh –  May 21 '18 at 15:50
  • @Frank This approach will indeed work and solves my problem. However, if you have something better, you are welcome to share it for the benefit of everyone! Thanks a lot! – TheFlyingObject May 21 '18 at 16:10
  • Looks like `Proxy Design Pattern` might work here. Let's hear expert advice though. – seccpur May 21 '18 at 16:18

2 Answers2

3

You can achieve the extra protection by making SharedData an opaque type

library.h

class SharedData;

void foo(SharedData*);

class bar {
public:
  void method(SharedData*);
};

library_private.h

class SharedData {
public:
  int x;
};

library.cpp

#include "library.h"
#include "library_private.h"

void foo(SharedData* d) {
  d->x = 0;
}

void bar::method(SharedData* d) {
  d->x = 1;
}

This way, only cpp files including library_private.h have access to SharedData's interface, but SharedData instances can still be passed around the rest of the project.

The only tricky part is managing the lifetime of SharedData, since you will have to allocate SharedData on the heap most of the time, and smart pointers will have to have custom deleters attached to them. So opaque types are often wrapped up in some wrapper type to manage RAII.

Something like this:

in header:

class SharedDataImpl;
class SharedData {
public:
  SharedData();
  ~SharedData();

  SharedDataImpl* get() {
    return impl_.get();
  }
private:
  std::unique_ptr<SharedDataImpl> impl_;
};

in .cpp:

SharedData() 
  : impl_(std::make_unique<SharedDataImpl>()) {}

~SharedData() {}
  • Your answer points in the right direction. I did not think of this approach. However, I think I was not able to communicate the problem clearly. Please see the question as I have edited it. – TheFlyingObject May 21 '18 at 15:43
2

You might use PassKey idiom, something like:

class SharedData
{
public:
    class FlagKey
    {
        friend class ConcreteStateA;
        // List here classes which can modify Flag
    private:
        FlagKey() {}
        FlagKey(const FlagKey&) = delete;
    };

    class VarKey
    {
        friend class ConcreteStateB;
        // List here classes which can modify Var
    private:
        VarKey() {}
        VarKey(const FlagKey&) = delete;
    };

public:
    double GetVar() const {return var;}
    bool GetFlag() const {return flag;}
    void SetVar(VarKey, double in_var) {var = in_var;}
    void SetFlag(FlagKey, bool in_flag) {flag = in_flag;}
private:
    double var = 0;
    bool flag = false;
};

And then:

class ConcreteStateA : public StateIface
{
public:
    virtual void Run(SharedData& data) final {
        // data.SetVar({}, 0); // error: calling a private constructor of class 'VarKey'
        data.SetFlag({}, false);
    }
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302