多个 Pimpl-类 互相使用

Multiple Pimpl-classes using each other

我通过 pimpl 向用户公开了三个 classes。 Model 是一个可以从文件读取和写入文件的数据容器。 Manipulator 是一个对象,可以加载 Model、执行更改并将其 return 作为新的 ModelConsumer 加载一个 Model 并允许用户使用它(读取它的属性、打印它等)。

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;
};

使用 pimpl 的原因主要是为了隐藏 Model 使用的内部数据类型。用户可见的唯一类型应该是 ModelManipulatorConsumer 以及标准 c++ 类型。

我在这里遇到的问题是在实现 ConsumerImplManipulatorImpl 中:在那些 classes 中,我必须访问 ModelImpl 的底层数据结构,但 pimpl 隐藏了它们:

Consumer::ConsumerImpl::loadModel(Model model) {
    auto someModelValue = model.mImpl->someInternalValue;
}

显然这是行不通的。如何解决这个问题? pimpl 是正确的解决方案吗?

编辑: 我的同事想到了这个:

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

基本上模型有一个方法,return是一个指向(隐藏)内部数据的空指针。消费者可以获得这个指针,将其转换回正确的类型并访问数据。为了使其只能从消费者 class 访问,该方法是私有的,但 friends 与消费者。

我实施了这种方法并且对我有用。还是很好奇大家怎么看,有没有什么问题。

您面临的问题与 Pimpl 习语并没有真正的关系 - 假设您删除了代码段中与 pimpl 相关的部分,问题仍然存在,因为它是由需要访问模型的私有表示引起的(或 ModelImpl)实例。这就是我尝试处理这种情况的方式:

  • 定义一个操作列表,使 Consumer 实例可以调用 Model 实例。这些应该是模型的 public 接口的一部分。对模型和操纵器之间的关系执行相同的操作。如果这使您的模型接口过于混乱,请将其拆分为两个单独的抽象 classes,让模型实现从两者继承并 Consumer/Maninpulator 在为它们准备的基础 class 接口上运行。
  • 重新考虑模型的哪些部分值得隐藏。如果 Model 拥有一些需要从消费者访问的容器,请公开它(Effective STL,Item 2),通过适当的方法进行读取访问应该没问题。
  • 如果 Consumer 对象还需要更多的信息,他们自己的角色可能不仅仅是一个普通的消费者,所以也许他们的某些部分实现应该在另一个 class 或模型中实现?
  • 引入 a(n)(抽象)class 用于模型和消费者之间的数据交换。将此从 Consumer 传递给 Model,让 Model 选择将哪些信息传递给中间层。但这已经引入了一定程度的复杂性,这些复杂性可能是完全不必要的。

无论您对设计进行何种更改,您仍然可以选择是否使用带有转发方法的 Pimpl 习惯用法。最后一个建议:不要声明 classes friend 中的一个 - 这可能很固执己见,但您的场景并不表明这种强耦合是必要的。