1

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.

Johannes
  • 3,300
  • 2
  • 20
  • 35
  • Pimpl might be the right choice, but perhaps the design is flawed in other ways if you need to access other classes private data? – Some programmer dude Jun 29 '18 at 12:46
  • Thats possible. I am sure this is a very common thing, but I only have a few months of c++ experience and I am still struggeling with many design choices. Any ideas how to improve my design? – Johannes Jun 29 '18 at 12:54
  • 1
    If it's a friend it can just access `mImpl`, no casting to void* needed. – rustyx Jun 29 '18 at 16:20
  • True, thats pretty nice. I think i'l go with this solution. – Johannes Jun 29 '18 at 19:24

1 Answers1

3

The issue you are facing isn't really related to the Pimpl idiom - imagine you erase the pimpl related parts of your snippets, the problem stays the same as it's caused by the need to access the private representation of your Model (or ModelImpl) instance. This is how I would try to approach the situation:

  • Define a list of operations that make sense for a Consumer instance to invoke on a Model instance. These should be part of Model's public interface. Do the same for the relationship between Model and Manipulator. If that clutters your Model interface too much, split it into two separate abstract classes, let a Model implementation inherit from both and Consumer/Maninpulator operate on the base class interface that's meant for them.
  • Reconsider which parts of Model are worth hiding. If Model owns some container that needs to be accessed from a consumer, expose it (Effective STL, Item 2), read access via an appropriate method should be fine.
  • If Consumer objects still need more information, their own role could be more than an ordinary consumer, so maybe some parts of their implementation should be implemented in another class or in the model?
  • Introduce a(n) (abstract) class for data exchange between Model and Consumer. Pass this from Consumer to Model, let the Model choose which information to pas to the intermediate layer. But this already introduces a certain amount of complexity that might be quite unnecessary.

Whatever changes you apply to your design, you can still choose whether to use the Pimpl idiom with forwarding methods or not. And one last suggestion: don't declare one of the classes friend - this might be quite opinionated but your scenario doesn't suggest that such a strong coupling is necessary.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • Your answer tells me that there is no obvious solution that I overlooked but my design is flawed. Thanks! I really cannot expose any of models internal data, because the types are from a thirdparty library and I am building my code as a static library with a single header. So all thirdparty types should be completely invisible. – Johannes Jun 29 '18 at 13:04
  • Ok, that makes sense, then. You could still create individual wrappers around the thirdparty data types, doing nothing but hiding this representation. Then, the Model can focus on the logic it wants to provide and yield access to these wrappers. Though I can't judge whether that makes sense for the specific application. – lubgr Jun 29 '18 at 13:10
  • How does that solve my problem? The Consumer needs to access the internal data structures of the model directly (its not possible to wrap it in standard data types). – Johannes Jun 29 '18 at 13:17
  • It doesn't solve your problem, it encapsulates the thirdparty data. But if Consumer objects really need to be hard-wired to the data representation, the model becomes kind of useless, right? You might then also consider simplifying things by putting concrete third party data types into a struct (if there's no invariance a model should maintain) and let Consumer and Manipulator objects operate on that. – lubgr Jun 29 '18 at 13:23
  • The purpose of the Model is, that I can load the Model in the Manipulator, change the Model and then load the changed Model in the Consumer without creating new Consumers every time. – Johannes Jun 29 '18 at 13:30
  • @Johannes A reasonable requirement: A) "The Consumer needs to access the internal data structures ... " and (imho) a suitable proposal B) "Define a list of operations ...". In what way does a 'public' (accessor) method expose internal pimpl data structures? Example: How does the public method Model::toFile(...) expose any impl details? – 2785528 Jun 29 '18 at 14:02
  • Not sure if I got this right, but I don't see how this helps. The Accessor method would have to return the internal types, which is exactly what I want to avoid. To be more specific: `Model` is a container for several graph objects, their type is the internal type of a graph library. `Consumer` is an object that does some heavy computation on those graphs. – Johannes Jun 29 '18 at 14:28
  • Found a possible solution. Please check out the edit I made to my first post. – Johannes Jun 29 '18 at 16:12
  • Making Consumer a `friend` class seems to solve your problem. In my answer, I advised against using it, because it's the strongest coupling two classes can have. But why not, a pinch of pragmatism doens't hurt :) – lubgr Jul 01 '18 at 19:48