Почему clang не оптимизирует это с NRVO?
Я пытаюсь понять, почему достаточно хороший компилятор C++ 11 (clang) не оптимизирует этот код, и интересно, есть ли у кого-нибудь здесь мнения.
#include <iostream>
#define SLOW
struct A {
A() {}
~A() { std::cout << "A d'torn"; }
A(const A&) { std::cout << "A copyn"; }
A(A&&) { std::cout << "A moven"; }
A &operator =(A) { std::cout << "A copy assignmentn"; 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, но это, казалось бы, такой распространенный простой случай.
в общем, я стараюсь поощрять код" медленного " стиля, потому что мне намного легче отлаживать.
2 ответов
простой ответ: потому что в этом случае не разрешается применять copy elision. Компилятор разрешен только в очень немногих и конкретных случаях применять copy elision. Цитата из стандарта-12.8 [class.копия] пункт 31:
... Это выделение операций копирования / перемещения, называемое copy elision, разрешено при следующих обстоятельствах (которые могут быть объединены для устранения нескольких копий):
- в операторе return в функции С класса Тип возврата, когда выражение является именем энергонезависимой автоматический объект (кроме функции или предложении catch параметр) с тем же резюме-неквалифицированный тип, как функция возвращает тип, операции копирования/перемещения может быть опущен за счет построения автоматических объекта непосредственно в функцию-возвращаемое значение
- [...]
явно тип B(a)
не A
, т. е. копирование elision не разрешено. Остальные пули в тот же абзац относится к таким вещам, как throw
выражения, удаляющие копии из временного объявления и объявления исключений. Ни одно из них не применимо.
копия, которую вы видите в медленном пути, вызвана не отсутствием РВО, а тем, что в B (MakeA ())" MakeA () "является rvalue, но в B (a)" a " является lvalue.
чтобы сделать это ясно, давайте изменим медленный путь, чтобы указать, где 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());
таким образом, РВО произошло.
исправление, которое удаляет все копии, является:
return B(std::move(a));