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:
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:
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?
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...
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);
}
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With