Постепенно постройте объект
Предположим, что существует иерархия из двух классов (class Derived: public Base
). Оба этих класса имеют большой объем памяти и дорогостоящие конструкторы. Обратите внимание: ничего в этих классах не выделяется в куче: они просто имеют большой sizeof
.
Тогда есть функция с быстрым путем (выполняется всегда) и медленным путем (выполняется условно). Для быстрого пути нужен экземпляр Base
, а для медленного пути требуется экземпляр Derived
, построенный из существующей базы. Кроме того, решение медленного пути может быть выполнено только после быстрого пути.
Текущий код выглядит следующим образом:
void f()
{
Base base;
/* fast path */
if (need_slow_path) {
Derived derived (base);
/* slow path */
}
}
Это неэффективно, потому что база должна быть скопирована в производную; также база распределяется дважды, и существует риск. Что я хочу иметь:
- выделить память для
Derived
экземпляра
- вызов
Base
ctor на нем
- выполнить быстрый путь
- при необходимости вызовите
Derived
ctor в существующий экземпляр Base
и выполните медленный путь
Возможно ли это в С++? Если нет, какие возможные обходные пути? Очевидно, я пытаюсь оптимизировать скорость.
Ответы
Ответ 1
Я боюсь, что это невозможно, так как вы написали - любой конструктор Derived
должен вызывать конструктор субобъекта Base
, поэтому единственный способ сделать это юридически - вызвать сначала Base
destructor, и я считаю, что вы этого не хотите.
Однако это должно быть легко решить с небольшой редизайном - предпочесть состав над наследованием и сделать Derived
отдельным классом, который будет хранить ссылку (в общем смысле, она может быть указателем), чтобы Base
и использовать его. Если контроль доступа является проблемой, я чувствую, что friend
здесь оправдан.
Ответ 2
Вы должны немного изменить свой дизайн, чтобы изменить свою зависимость от наследования до композиции.
Вы можете инкапсулировать члены производного класса (не присутствующие в базовом классе) в другой класс и сохранить его нулевую ссылку в производном классе.
Теперь непосредственно инициализируйте производный класс без инициализации нового объекта класса.
Если требуется медленный путь, вы можете его инициализировать и использовать.
Преимущества
- Сохранение отношения между производным и базовым классом сохраняется.
- Объект базового класса никогда не копируется.
- У вас есть ленивая инициализация производного класса.
Ответ 3
Я могу подделать его.
Переместить/все данные производного в optional
(будь то предложение boost
или std::ts::optional
для пост-С++ 14 или вручную).
Iff вам нужен медленный путь, инициализируйте optional
. В противном случае оставьте его как nullopt
.
Накладные расходы bool
будут проверяться при назначении/сравнении/уничтожении неявных. И такие вещи, как virtual
функции, будут derived
(т.е. Вам нужно управлять динамическим расписанием вручную).
struct Base {
char random_data[1000];
// virtual ~Base() {} // maybe, if you intend to pass it around
};
struct Derived:Base {
struct Derived_Data {
std::string non_trivial[1000];
};
boost::optional< Derived_Data > m_;
};
теперь мы можем создать derived
, и только после того, как мы m_.emplace()
построим Derived_Data
. Все, что все еще существует, находится в одном смежном блоке памяти (с bool
, введенным optional
для отслеживания, если была построена m_
).
Ответ 4
Не уверен, что вы можете сделать то, что хотите. Я выполняю "быстрый" путь перед вторым конструктором, но я думаю, что вы используете функцию "размещение нового" - ручные вызовы на основе предиката need_slow_path
. но это немного изменилось:
- выделить память для производного экземпляра
- вызов Base или Derived ctor на нем
- выполнить быстрый путь
- выполнить медленный путь (если необходимо (
Пример кода
#include <memory>
void f(bool need_slow_path)
{
char bufx[sizeof(Derived)];
char* buf = bufx;
Derived* derived = 0;
Base* base = 0;
if (need_slow_path ) {
derived = new(buf) Derived();
base = derived;
} else {
base = new(buf) Base();
}
/* fast path using *base */
if (need_slow_path) {
/* slow path using *base & * derived */
}
// manually destroy
if (need_slow_path ) {
derived->~Derived();
} else {
base->~Base();
}
}
Размещение нового хорошо описано здесь: Что используется для "размещения нового" ?
Ответ 5
Можете ли вы определить перемещение copy con't в свой компилятор?
Здесь есть отличное объяснение (хотя и немного длинное)
https://skillsmatter.com/skillscasts/2188-move-semanticsperfect-forwarding-and-rvalue-references
У меня нет опыта с семантикой перемещения, поэтому я могу ошибаться, но поскольку вы хотите избежать совпадения базового объекта при передаче его семантике производного класса, следует сделать трюк
Ответ 6
Сначала извлеките код конструктора в методы инициализации как для Base
, так и Derived
.
Тогда я бы сделал код похожим на это:
void f()
{
Derived derived;
derived.baseInit();
/* fast path */
if (need_slow_path) {
derived.derivedInit();
/* slow path */
}
}
Это хорошая идея, чтобы извлечь классы и использовать композицию, как предложил Танмай Патил в своем ответе.
И еще один намек: если вы еще не сделали этого, погрузитесь в Unit-Tests. Они помогут вам справиться с огромными классами.
Ответ 7
Возможно, вместо класса и конструкторов вам понадобятся функции plain-old-struct и initialization. Конечно, вы откажетесь от многих удобств на С++, но сможете реализовать свою оптимизацию.