Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ data sharing between objects/classes implementing the same interface

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...

like image 630
TheFlyingObject Avatar asked Sep 26 '26 11:09

TheFlyingObject


2 Answers

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() {}

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);
    }
};
like image 24
Jarod42 Avatar answered Sep 29 '26 01:09

Jarod42