C++ универсальная ссылка в конструкторе и оптимизации возвращаемого значения (rvo)

Почему оптимизация rvalue не происходит в классах с конструктором с универсальными ссылочными аргументами?

http://coliru.stacked-crooked.com/a/672f10c129fe29a0

#include <iostream>

 template<class ...ArgsIn>
struct C {

  template<class ...Args>
  C(Args&& ... args) {std::cout << "Ctr\n";}        // rvo occurs without &&

  ~C(){std::cout << "Dstr\n";}
};

template<class ...Args> 
auto f(Args ... args) {
    int i = 1;
  return C<>(i, i, i);
}

int main() {
  auto obj = f();
}

Выход:

Ctr
Ctr
Dstr
Ctr
Dstr
Dstr

1 ответ

Решение

Я считаю, что проблема заключается в том, что

template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}  

не являются конструкторами копирования / перемещения в том, что касается языка, и поэтому вызовы к ним не могут быть исключены компилятором. Начиная с §12.8 [class.copy]/p2-3, акцент добавлен, а примеры опущены:

Не шаблонный конструктор для класса X является конструктором копирования, если его первый параметр имеет тип X&, const X&, volatile X& или же const volatile X&и либо нет других параметров, либо все остальные параметры имеют аргументы по умолчанию (8.3.6).

Не шаблонный конструктор для класса X является конструктором перемещения, если его первый параметр имеет тип X&&, const X&&, volatile X&&, или же const volatile X&&и либо нет других параметров, либо все остальные параметры имеют аргументы по умолчанию (8.3.6).

Другими словами, конструктор, который является шаблоном, никогда не может быть конструктором копирования или перемещения.

Оптимизация возвращаемого значения - это особый случай исключения копирования, который описывается как (§12.8 [class.copy]/p31):

При соблюдении определенных критериев реализация может опустить конструкцию копирования / перемещения объекта класса, даже если конструктор, выбранный для операции копирования / перемещения и / или деструктор для объекта, имеет побочные эффекты.

Это позволяет реализациям исключать "конструкцию копирования / перемещения"; создание объекта с использованием чего-либо, что не является ни конструктором копирования, ни конструктором перемещения, не является "конструкцией копирования / перемещения".

Так как C имеет пользовательский деструктор, неявный конструктор перемещения не генерируется. Таким образом, разрешение перегрузки выберет шаблонный конструктор с Args выводится как C, что является лучшим соответствием, чем неявный конструктор копирования для значений. Однако компилятор не может исключить вызовы этого конструктора, поскольку он имеет побочные эффекты и не является ни конструктором копирования, ни конструктором перемещения.

Если шаблонный конструктор вместо

template<class ...Args>
C(Args ... args) {std::cout << "Ctr\n";} 

Тогда это не может быть реализовано с Args знак равно C создать конструктор копирования, так как это приведет к бесконечной рекурсии. В стандарте есть специальное правило, запрещающее такие конструкторы и реализации (§12.8 [class.copy]/p6):

Объявление конструктора для класса X неправильно сформирован, если его первый параметр имеет тип (необязательно cv-qualified) X и либо нет других параметров, либо все остальные параметры имеют аргументы по умолчанию. Шаблон функции-члена никогда не создается для создания такой сигнатуры конструктора.

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

Если вместо этого мы удалим пользовательский деструктор из Cи добавьте еще один класс, чтобы отслеживать, когда CВместо этого вызывается деструктор:

struct D {
    ~D() { std::cout << "D's Dstr\n"; }
};

template<class ...ArgsIn>
struct C {
  template<class ...Args>
  C(Args&& ... args) {std::cout << "Ctr\n";}
  D d;
};

Мы видим только один звонок Dдеструктор, указывающий, что только один C Объект построен. Вот CКонструктор перемещения неявно генерируется и выбирается с помощью разрешения перегрузки, и вы снова видите, как RVO срабатывает.

Другие вопросы по тегам