I have three classes that I expose to the user via pimpl. The Model
is a data container that can be read from and written to a file. The Manipulator
is an object that can load a Model
, perform changes and return it as a new Model
. The Consumer
loads a Model
and allows the user to things with it (read its properties, print it etc.).
class Model {
Model(std::string &filename);
void toFile(std::string &filename);
private:
class ModelImpl;
std::unique_ptr<ModelImpl> mImpl;
};
class Manipulator {
Manipulator(Model &model);
Model alterModel(...);
private:
class ManipulatorImpl;
std::unique_ptr<ManipulatorImpl> mImpl;
};
class Consumer {
Consumer(Model &model);
void loadModel(Model &model);
void doSomething();
private:
class ConsumerImpl;
std::unique_ptr<ConsumerImpl> mImpl;
};
The reason for using pimpl is primarily to hide the internal data types used by the Model
. The only types visible to the user should be Model
, Manipulator
and Consumer
and standard c++ types.
The issue I am having here is in the implementations ConsumerImpl
and ManipulatorImpl
: in those classes I have to access the underlying data structures of the ModelImpl
, but pimpl hides them:
Consumer::ConsumerImpl::loadModel(Model model) {
auto someModelValue = model.mImpl->someInternalValue;
}
Obviously this does not work. How to solve this? Is pimpl the right solution here?
Edit: My colleague came up with this:
class Consumer {
Consumer(Model &model);
void loadModel(Model &model);
void doSomething();
private:
class ConsumerImpl;
std::unique_ptr<ConsumerImpl> mImpl;
};
class Model {
Model(std::string &filename);
void toFile(std::string &filename);
private:
void *getObjectPtr();
class ModelImpl;
std::unique_ptr<ModelImpl> mImpl;
friend Consumer;
};
void *Model::Model::getObjectPtr() {
return mImpl->getObjectPtr();
}
class Model::ModelImpl {
public:
// [...]
void *getObjectPtr();
private:
SecretInternalType mData;
};
void *Model::ModelImpl::getObjectPtr() {
return static_cast<void*>(&mData);
}
// Access the internal data of Model from Consumer:
void Consumer::ConsumerImpl::doSomething() {
SecretInternalType* data = static_cast<SecretInternalType*>(mModel->getObjectPtr());
}
Basically the Model has a method that returns a void pointer to the (hidden) internal data. The consumer can get this pointer, cast it back to the correct type and access the data. To make this only accessable from the Consumer class, the method is private but friends
with Consumer.
I implemented this approach and it works for me. Still I am curious what you think of it and if there are any problems.