多个 Pimppl 类相互使用

Multiple Pimpl-classes using each other

本文关键字:Pimppl 多个      更新时间:2023-10-16

我有三个类,我通过pimpl向用户公开。Model是可以读取和写入文件的数据容器。Manipulator是一个对象,可以加载Model,执行更改并将其作为新Model返回。Consumer加载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:在这些类中,我必须访问ModelImpl的底层数据结构,但 pimpl 隐藏了它们:

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

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

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

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

基本上,模型有一个返回指向(隐藏(内部数据的 void 指针的方法。使用者可以获取此指针,将其转换回正确的类型并访问数据。若要使其只能从 Consumer 类访问,该方法是私有的,但与 Consumerfriends

我实施了这种方法,它对我有用。我仍然很好奇你怎么看,是否有任何问题。

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

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

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