У меня есть три класса, которые я предоставляю пользователю через 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
. Единственными видимыми для пользователя типами должны быть Model
, Manipulator
и Consumer
и стандартные типы c ++.
Проблема, с которой я столкнулся здесь, заключается в реализациях ConsumerImpl
и ManipulatorImpl
: в этих классах я должен получить доступ к базовым структурам данных 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());
}
По сути, в модели есть метод, который возвращает пустой указатель на (скрытые) внутренние данные. Потребитель может получить этот указатель, привести его обратно к правильному типу и получить доступ к данным. Чтобы сделать это доступным только из класса Consumer, метод является закрытым, но friends
с Consumer.
Я реализовал этот подход, и он работает для меня. Тем не менее, мне любопытно, что вы об этом думаете и есть ли какие-либо проблемы.