多个 Pimpl-类 互相使用
Multiple Pimpl-classes using each other
我通过 pimpl 向用户公开了三个 classes。 Model
是一个可以从文件读取和写入文件的数据容器。 Manipulator
是一个对象,可以加载 Model
、执行更改并将其 return 作为新的 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
使用的内部数据类型。用户可见的唯一类型应该是 Model
、Manipulator
和 Consumer
以及标准 c++ 类型。
我在这里遇到的问题是在实现 ConsumerImpl
和 ManipulatorImpl
中:在那些 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
中的一个 - 这可能很固执己见,但您的场景并不表明这种强耦合是必要的。
我通过 pimpl 向用户公开了三个 classes。 Model
是一个可以从文件读取和写入文件的数据容器。 Manipulator
是一个对象,可以加载 Model
、执行更改并将其 return 作为新的 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
使用的内部数据类型。用户可见的唯一类型应该是 Model
、Manipulator
和 Consumer
以及标准 c++ 类型。
我在这里遇到的问题是在实现 ConsumerImpl
和 ManipulatorImpl
中:在那些 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
中的一个 - 这可能很固执己见,但您的场景并不表明这种强耦合是必要的。