Подтвердить что ты не робот

Постепенно постройте объект

Предположим, что существует иерархия из двух классов (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 и выполните медленный путь

Возможно ли это в С++? Если нет, какие возможные обходные пути? Очевидно, я пытаюсь оптимизировать скорость.

4b9b3361

Ответ 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. Конечно, вы откажетесь от многих удобств на С++, но сможете реализовать свою оптимизацию.