У меня есть иерархия классов, для которой я хотел бы отделить интерфейс от реализации. Мое решение состоит в том, чтобы иметь две иерархии: иерархию дескрипторов классов для интерфейса и иерархию закрытых классов для реализации. Базовый класс дескриптора имеет указатель на реализацию, которую производные классы дескриптора приводят к указателю производного типа (см. Функцию getPimpl()
).
Вот эскиз моего решения для базового класса с двумя производными классами. Есть ли лучшее решение?
Файл "Base.h":
#include <memory>
class Base {
protected:
class Impl;
std::shared_ptr<Impl> pImpl;
Base(Impl* pImpl) : pImpl{pImpl} {};
...
};
class Derived_1 final : public Base {
protected:
class Impl;
inline Derived_1* getPimpl() const noexcept {
return reinterpret_cast<Impl*>(pImpl.get());
}
public:
Derived_1(...);
void func_1(...) const;
...
};
class Derived_2 final : public Base {
protected:
class Impl;
inline Derived_2* getPimpl() const noexcept {
return reinterpret_cast<Impl*>(pImpl.get());
}
public:
Derived_2(...);
void func_2(...) const;
...
};
Файл "Base.cpp":
class Base::Impl {
public:
Impl(...) {...}
...
};
class Derived_1::Impl final : public Base::Impl {
public:
Impl(...) : Base::Impl(...) {...}
void func_1(...) {...}
...
};
class Derived_2::Impl final : public Base::Impl {
public:
Impl(...) : Base::Impl(...) {...}
void func_2(...) {...}
...
};
Derived_1::Derived_1(...) : Base(new Derived_1::Impl(...)) {...}
Derived_1::func_1(...) const { getPimpl()->func_1(...); }
Derived_2::Derived_2(...) : Base(new Derived_2::Impl(...)) {...}
Derived_2::func_2(...) const { getPimpl()->func_2(...); }
Base
, нормального абстрактного базового класса («интерфейс») и конкретных реализаций без pimpl может быть достаточно.Ответы:
Я думаю, что это плохая стратегия, чтобы
Derived_1::Impl
извлечь из этогоBase::Impl
.Основная цель использования идиомы Pimpl - скрыть детали реализации класса. Давая
Derived_1::Impl
исходить изBase::Impl
, вы победили эту цель. Теперь не только делает осуществлениеBase
зависитBase::Impl
, реализацияDerived_1
также зависитBase::Impl
.Это зависит от того, какие компромиссы приемлемы для вас.
Решение 1
Сделайте
Impl
занятия полностью независимыми. Это будет означать, что будет два указателя наImpl
классы - один в,Base
а другой вDerived_N
.Решение 2
Выставляйте классы только как ручки. Не раскрывайте определения классов и реализации вообще.
Публичный заголовочный файл:
Вот быстрая реализация
Плюсы и минусы
При первом подходе вы можете создавать
Derived
классы в стеке. При втором подходе это не вариант.При первом подходе вы платите два динамических выделения и освобождения для создания и уничтожения
Derived
в стеке. Если вы строите и уничтожаетеDerived
объект из кучи, вы несете расходы на еще одно выделение и освобождение. При втором подходе вы платите только за одно динамическое распределение и одно освобождение для каждого объекта.При первом подходе вы получаете возможность использовать
virtual
функцию-членBase
. При втором подходе это не вариант.Мое предложение
Я бы пошел с первым решением, чтобы я мог использовать иерархию классов и
virtual
функции-члены,Base
хотя это немного дороже.источник
Единственное улучшение, которое я могу увидеть здесь, - позволить конкретным классам определять поле реализации. Если это необходимо абстрактным базовым классам, они могут определить абстрактное свойство, которое легко реализовать в конкретных классах:
Base.h
Base.cpp
Это кажется мне безопаснее. Если у вас есть большое дерево, вы также можете ввести
virtual std::shared_ptr<Impl1> getImpl1() =0
в середине дерева.источник