Почему Clang не оптимизирует это с NRVO?

Я пытаюсь понять, почему достаточно хороший компилятор C ++ 11 (clang) не оптимизирует этот код, и мне интересно, есть ли у кого-нибудь здесь мнения.

#include <iostream>
#define SLOW

struct A {
A() {}
~A() { std::cout << "A d'tor\n"; }
A(const A&) { std::cout << "A copy\n"; }
A(A&&) { std::cout << "A move\n"; }
A &operator =(A) { std::cout << "A copy assignment\n"; return *this; }
};

struct B {
// Using move on a sink.
// Nice talk at Going Native 2013 by Sean Parent.
B(A foo) : a_(std::move(foo)) {}
A a_;
};

A MakeA() {
return A();
}

B MakeB() {
// The key bits are in here
#ifdef SLOW
A a(MakeA());
return B(a);
#else
return B(MakeA());
#endif
}

int main() {
std::cout << "Hello World!\n";
B obj = MakeB();
std::cout << &obj << "\n";
return 0;
}

Если я запускаю это с #define SLOW закомментировано и оптимизировано с -s я получил

Hello World!
A move
A d'tor
0x7fff5fbff9f0
A d'tor

что ожидается.

Если я запускаю это с #define SLOW включен и оптимизирован с -s Я получил:

Hello World!
A copy
A move
A d'tor
A d'tor
0x7fff5fbff9e8
A d'tor

Что, очевидно, не так приятно. Итак, вопрос:

Почему я не вижу оптимизации NRVO, примененной в случае «МЕДЛЕННО»? Я знаю, что компилятору не требуется применять NRVO, но это может показаться довольно распространенным простым случаем.

В общем, я стараюсь поощрять код в стиле «SLOW», потому что мне гораздо легче отлаживать.

7

Решение

Ответ прост: потому что в этом случае не разрешается применять elision. Компилятору разрешено применять только в очень немногих и особых случаях применять разрешение на копирование. Цитата из стандарта 12.8 [class.copy] параграф 31:

… Такое исключение операций копирования / перемещения, называемое разрешением копирования, допускается при следующих обстоятельствах (которые могут быть объединены для удаления нескольких копий):

  • в операторе return в функции с типом возврата класса, когда выражение является именем энергонезависимого автоматического объекта (кроме параметра функции или catch-clause) с тем же типом cv unqualified, что и тип возврата функции, Операция копирования / перемещения может быть опущена путем создания автоматического объекта непосредственно в возвращаемое значение функции
  • […]

Очевидно, тип B(a) не является Aто есть копирование не разрешено. Другие пули в том же пункте относятся к таким вещам, как throw выражения, дубликаты из временной и декларации исключений. Ни один из них не применяется.

13

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

Копия, которую вы видите на медленном пути, вызвана не отсутствием RVO, а тем, что
в B (MakeA ()) «MakeA ()» является r-значением, но в B (a) «a» является l-значением.

Чтобы сделать это понятным, давайте изменим медленный путь, чтобы указать, где завершается MakeA ():

#ifdef SLOW
A a(MakeA());
std::cout << "---- after call \n";
return B(a);
#else

Выход:

Hello World!
---- after call
A copy
A move
A d'tor
A d'tor
0x7fff5a831b28
A d'tor

Что показывает, что в

A a(MakeA());

Таким образом, RVO действительно произошло.

Исправление, которое удаляет все копии:

return B(std::move(a));
2

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