шаблоны — 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

9

Решение

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

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, что является лучшим соответствием, чем неявный конструктор копирования для значений r. Однако компилятор не может исключить вызовы этого конструктора, поскольку он имеет побочные эффекты и не является ни конструктором копирования, ни конструктором перемещения.

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

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 срабатывает.

10

Другие решения


По вопросам рекламы [email protected]