Работает ли оптимизация возвращаемого значения при назначении другому типу?

Рассмотрим следующие два класса:

class Base  
{  
  Base(const Base& other) {...} // relatively expensive operations here...
  Base(int i)             {...} // ...here,
  virtual ~Base()         {...} // ...and here
  ...
};

class Derived : public Base
{
  ...
  Derived(const Base& other)   :Base(other) {...} // some typechecking in here 
  virtual ~Derived() {}
  ...
};

Это означает, что Base можно преобразовать с помощью второго конструктора Derived. Теперь рассмотрим следующий код:

Base getBase()  
{
   int id = ...
   return Base(id);
}
...
int main()
{
   Base    b = getBase();   // CASE 1
   Derived d1(b);           // "upcast"

   Derived d2 = getBase();  // CASE 2
   ...
}

Я использую VS2008 с включенной оптимизацией (/Ox/Ob2/Oi/Ot). Я проверил вызовы конструкторов и деструкторов на консольном выходе:

В Случае 1 оптимизация возвращаемого значения работает. Есть два вызова:

  1. База (целое число)
  2. ~ База ()

Однако тут нечего выигрывать, когда Derived-объект нужен в main. Для «upcast» требуется другая пара конструктор/деструктор.

В Случае 2 оптимизация возвращаемого значения не работает. Здесь создаются и уничтожаются два объекта:

  1. Base(int) //Создать временное
  2. ~Base() //Уничтожить временно
  3. Base(const Base&) //через Derived(const Base&)
  4. ~Base() //через ~Derived()

Теперь мне кажется, что у меня есть три противоречивых требования:

  1. Я хотел бы избежать накладных расходов на создание временного объекта (поскольку создание и уничтожение объекта довольно затратно в классе Base)
  2. В main мне нужен Производный-объект вместо Base-объекта для работы.

Очевидно, что бесплатного обеда здесь не бывает. Но я мог что-то упустить. Итак, мой вопрос: есть ли способ объединить эти требования? Или у кого-то был подобный опыт?

Примечание: мне известен тот факт, что производный объект "upcast" (const Base и другие) может дать сбой во время выполнения (об этом позаботились). Поскольку код в порядке на синтаксическом уровне, я думаю, это не причина, по которой компилятор избегает RVO.


person osterhase    schedule 24.04.2011    source источник
comment
RVO относится к устранению копии, необходимой оператору return внутри функции при создании возвращаемого значения. Если возвращаемое значение используется в качестве инициализатора для объекта, то возвращаемое значение может быть скопировано в объявленный объект или эта копия может быть устранена, а объект инициализирован непосредственно возвращаемым значением функции. Эта вторая копия не является оптимизацией возвращаемого значения, и я думаю, что вы имеете в виду именно эту копию.   -  person CB Bailey    schedule 24.04.2011
comment
Итак, в случае 1 нет вызова копировщику Base? Я не думаю, что это законно - b и d1 являются отдельными объектами, и подобъект базового класса d1 должен быть создан из b. Исключение конструктора-копирования допускает исключение только временных объектов, а не переменных. [Редактировать: о, подождите, вы имеете в виду, что он есть, но вы его не указали? требуется другая пара ctor/dtor. Я бы сказал, что RVO работает, но, как вы видели, он не делает то, что вам нужно.]   -  person Steve Jessop    schedule 24.04.2011


Ответы (2)


Это плохо.

Derived(const Base& other)   :Base(other) {...}

Статический тип other может принадлежать производному типу. В этом случае он будет нарезан. Кроме того, этот базовый класс будет скопирован в любом случае.

RVO заключается в обходе конструктора копирования и инициализации возвращаемого объекта на месте. Если вам нужен объект производного типа, вам придется сначала его создать. RVO не может построить его для вас.

Вместо Derived(const Base& other) вы можете рассмотреть другой подход. Как насчет этого:

class Base  
{
  ...
  // extract expensive parts of another instance
  virtual void initialise(Base& b);
  ...
};

class Derived : public Base
{
  ...
  Derived(); // cheap constructor
  void initialise(Base& b) { /* implementation goes here */  }
  ...
};

initialise(Base& b) будет извлекать из аргумента дорогие части. Это может быть разрушительным. База предоставит общедоступный (или, возможно, защищенный) интерфейс для фактического извлечения.

person pic11    schedule 24.04.2011

Как насчет добавления конструктора в Derived?

Derived(Base (*f)(void)) : Base(f()) { ... }

Тогда Derived d3 = getBase; может дать вам желаемую оптимизацию. Вероятно, это не очень практично, так как мне пришлось указать пустой список параметров f в Derived, что весьма ограничивает. Но сделайте его конструктором шаблона, и вы можете использовать пользовательский функтор, результат лямбды boost:bind или C++0x, где это возможно.

В противном случае извлеките id = ... часть getBase в функцию getId и дайте Derived конструктор, принимающий int, который передает id своему подобъекту Base. Несомненно, реальный код более сложен, чем этот, и это может привести к переносу большого количества параметров о месте. Может быть, легкий класс BaseParameters, который вы используете вместо Base до тех пор, пока вам действительно не понадобится медленная работа, а затем преобразуйте этот в Base, Derived или другой родственный класс.

person Steve Jessop    schedule 24.04.2011
comment
Звучит интересно. Я собираюсь попробовать это, чтобы увидеть эффекты. Тем не менее, это привело бы к связи между классом «Производный» и функцией «getBase()» (которая на самом деле представляет собой ряд подобных функций), чего я хотел бы избежать. - person osterhase; 25.04.2011
comment
@osterhase: зависит от того, как ты это делаешь. Если все аргументы для getBase захвачены bind или лямбдой, то Derived по-прежнему связан интерфейсом только с Base, а не getBase. Подойдет любая функция без аргументов, возвращающая Base, и этого достаточно, чтобы Derived изолировать Derived от подробностей того, какую функцию выбирает вызывающий для передачи и как она работает. Это внедрение зависимостей, которое используется только один раз и только в списке инициализаторов. - person Steve Jessop; 25.04.2011
comment
Но поскольку он задуман как способ получить оптимизацию, а не как необходимая часть желаемого интерфейса, очевидно, есть вероятность, что в каком-то отношении он будет неприятным... - person Steve Jessop; 25.04.2011