C++11 лажа?
я написал простенький тестик, который даёт неожидаемый результат (VS2013).
Вот он:
class DDD
{
void operator =(const DDD&);
void operator =(DDD&&);
public:
int xxx;
DDD() : xxx(0) {}
DDD(const DDD& a) : xxx(a.xxx + 1){}
DDD(DDD&& a) : xxx(a.xxx + 2){}
};
DDD ddd(){ return DDD(); }
int _tmain(int argc, _TCHAR* argv[])
{
DDD d = ddd();
cout << d.xxx << endl; // print 0
}
По моим объяснимым предположениям в конце d.xxx должно быть либо 1 (если сработал конструктор копии), либо 2 (если сработал конструктор перемещения), но результат неожиданный: 0.
Были подозрения что компилятор сделал функцию ddd() как инлайн, однако я её перемещал и в конец файла, добавил вначале лишь объявление, - результат
тот же.
Лажа в компиляторе?
DDD ddd(){ DDD d; return d; } выдаёт 2, т.е. сработало перемещение. Тут нареканий нет.
Здесь имеет место быть т.н. RVO (return value optimization) или NRVO (named return value optimization). Обе оптимизации являются частным случаем copy (move) elision.
В стандарте есть такая фраза:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects.
Т.о. компилятор имеет право полностью исключить вызов конструкторов копирования и перемещения (при определённых критериях). Это также зависит от реализации компилятора и настроек оптимизации.
- Проблема не в C++11. Подобные оптимизации существовали задолго до этого. Помню году в 2002 (когда я в аспирантуре пешком под парту ходил) об этом шли разговоры применительно к C++.
- Меня поражает, что во втором случае (где "нареканий нет") программа выдала другой результат. То место в стандарте, на которое указал dymanoid, относится к обоим случаям в равной степени. Таким образом, результат здесь зависит полностью от компилятора.
Я хоть уже всё и забыл, но что то не врублюсь в проблему. Всё работает как и должно при чём тут оптимизация?
#include "stdafx.h" #include <iostream> using namespace std; class DDD { void operator =(const DDD&); void operator =(DDD&&); public: int xxx; DDD() : xxx(0){ cout << "ctor1" << endl; } DDD(const DDD& a) : xxx(a.xxx + 1){ cout << "ctor2" << endl; } DDD(DDD&& a) : xxx(a.xxx + 2){ cout << "ctor3" << endl; } }; DDD ddd() { cout << "f1" << endl; return DDD(); } int _tmain(int argc, _TCHAR* argv[]) { DDD d = ddd(); cout << d.xxx << endl; // print 0 DDD d1 = DDD(d); cout << d1.xxx << endl; // print 1 DDD d2 = DDD(DDD()); // an rvalue reference cout << d2.xxx << endl; // print 2 return 0; } /* ---output--- f1 ctor1 0 ctor2 1 ctor1 ctor3 2 */
проблем нет. есть оптимизация, причем хорошая, а не вредная.
в случае DDD d = ddd(); без оптимизации должны сработать сперва конструктор по умолчанию (он явно указан внутри функции: DDD()), а затем конструктор копирования (здесь: DDD d = ).
Кстати даже два конструктора копирования могут сработать без оптимизации (кроме уже указанного конструктора по умолчанию). Первый на return а второй на = (что я уже указал выше).
Я хоть уже всё и забыл, но что то не врублюсь в проблему. Всё работает как и должно при чём тут оптимизация?
Вы правы, вы не поняли проблему.
Попробую разжевать.
Вызываем функцию ddd(). Эта функция создаёт объект типа DDD, поместив его в стек этой функции в качестве неименованного временного значения (unnamed temporary). Функция должна вернуть объект типа DDD по значению (by-value), соответственно, объект должен быть скопирован из неименованного временного значения в возвращаемое значение. Так бы поступил какой-нибудь древний компилятор. Современный компилятор (c++11 с семантикой перемещения) видит, что это самое неименованное временное значение нигде больше в функции ddd() не используется, поэтому этот компилятор может сгенерировать код перемещения объекта из временного значения во возвращаемое. Это и есть оптимизация (возможный её вариант). Таким образом возвращаются контейнеры стандартной библиотеки, например. Т.о. автор вопроса ожидал в этом случае увидеть вызов либо конструктора копирования (без оптимизации), либо конструктора перемещения (с оптимизацией по перемещению).
Но как я уже приводил выше, стандарт разрешает полностью исключать вызовы конструкторов копирования или перемещения в рамках RVO. В этом случае объект создаётся напрямую в стеке вызывающей функции, без создания неименованного временного значения, без копирования и перемещения.
Можете хотя бы на Википедии коротко про это почитать.
Мало знаком с MSVS, но у g++ есть опция -fno-elide-constructors которая скорее всего приведёт к вызову "конструктора перемещения" в Вашей программе. Я говорю "скорее всего" т.к. компилятор всё равно может застесняться, поскольку Ваш конструктор перемещения не имеет при себе noexcept. Ведь кому хочется, чтобы во время перемещения было заброшено исключение?
Конечно лажа, MSVS 2013 не имеет полной поддержки С++11 https://msdn.microsoft.com/en-us/library/hh567...