Deutsch
Germany.ruФорумы → Архив Досок→ Программирование

C++11 лажа?

500  
anly коренной житель19.08.16 10:43
anly
NEW 19.08.16 10:43 

я написал простенький тестик, который даёт неожидаемый результат (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, т.е. сработало перемещение. Тут нареканий нет.

Проклят нарушающий межи ближнего своего (Втор.27:17)
#1 
dymanoid постоялец19.08.16 14:48
dymanoid
NEW 19.08.16 14:48 
в ответ anly 19.08.16 10:43

Здесь имеет место быть т.н. 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.

Т.о. компилятор имеет право полностью исключить вызов конструкторов копирования и перемещения (при определённых критериях). Это также зависит от реализации компилятора и настроек оптимизации.

#2 
anly коренной житель19.08.16 17:08
anly
NEW 19.08.16 17:08 
в ответ dymanoid 19.08.16 14:48

похоже на то.

пусть оптимизирует.

в нормальной программе в подобной ситуации конструкторы копии и перемещения всё равно должны сделать такой же экземпляр.

Проклят нарушающий межи ближнего своего (Втор.27:17)
#3 
Krist5 прохожий20.08.16 02:11
20.08.16 02:11 
в ответ anly 19.08.16 10:43, Последний раз изменено 20.08.16 13:25 (Krist5)
  1. Проблема не в C++11. Подобные оптимизации существовали задолго до этого. Помню году в 2002 (когда я в аспирантуре пешком под парту ходил) об этом шли разговоры применительно к C++.
  2. Меня поражает, что во втором случае (где "нареканий нет") программа выдала другой результат. То место в стандарте, на которое указал dymanoid, относится к обоим случаям в равной степени. Таким образом, результат здесь зависит полностью от компилятора.
#4 
AlexNek патриот20.08.16 12:53
AlexNek
NEW 20.08.16 12:53 
в ответ anly 19.08.16 10:43

Я хоть уже всё и забыл, но что то не врублюсь в проблему. Всё работает как и должно при чём тут оптимизация?

#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
*/
#5 
anly коренной житель20.08.16 14:59
anly
NEW 20.08.16 14:59 
в ответ AlexNek 20.08.16 12:53, Последний раз изменено 20.08.16 15:03 (anly)

проблем нет. есть оптимизация, причем хорошая, а не вредная.

в случае DDD d = ddd(); без оптимизации должны сработать сперва конструктор по умолчанию (он явно указан внутри функции: DDD()), а затем конструктор копирования (здесь: DDD d = ).


Кстати даже два конструктора копирования могут сработать без оптимизации (кроме уже указанного конструктора по умолчанию). Первый на return а второй на = (что я уже указал выше).

Проклят нарушающий межи ближнего своего (Втор.27:17)
#6 
dymanoid постоялец20.08.16 15:09
dymanoid
NEW 20.08.16 15:09 
в ответ AlexNek 20.08.16 12:53
Я хоть уже всё и забыл, но что то не врублюсь в проблему. Всё работает как и должно при чём тут оптимизация?

Вы правы, вы не поняли проблему.

Попробую разжевать.

Вызываем функцию ddd(). Эта функция создаёт объект типа DDD, поместив его в стек этой функции в качестве неименованного временного значения (unnamed temporary). Функция должна вернуть объект типа DDD по значению (by-value), соответственно, объект должен быть скопирован из неименованного временного значения в возвращаемое значение. Так бы поступил какой-нибудь древний компилятор. Современный компилятор (c++11 с семантикой перемещения) видит, что это самое неименованное временное значение нигде больше в функции ddd() не используется, поэтому этот компилятор может сгенерировать код перемещения объекта из временного значения во возвращаемое. Это и есть оптимизация (возможный её вариант). Таким образом возвращаются контейнеры стандартной библиотеки, например. Т.о. автор вопроса ожидал в этом случае увидеть вызов либо конструктора копирования (без оптимизации), либо конструктора перемещения (с оптимизацией по перемещению).


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


Можете хотя бы на Википедии коротко про это почитать.

#7 
  moose местный житель20.08.16 15:30
NEW 20.08.16 15:30 
в ответ dymanoid 20.08.16 15:09

При чем здесь С++11?

https://msdn.microsoft.com/en-us/library/ms364...

#8 
dymanoid постоялец20.08.16 15:56
dymanoid
NEW 20.08.16 15:56 
в ответ moose 20.08.16 15:30

При том что автор вопроса ожидал увидеть конструктор перемещения, который есть только с 11.

Сама RVO известна ещё со времён царя Гороха.

#9 
Krist5 прохожий20.08.16 20:01
NEW 20.08.16 20:01 
в ответ dymanoid 20.08.16 15:56, Последний раз изменено 21.08.16 00:35 (Krist5)

Мало знаком с MSVS, но у g++ есть опция -fno-elide-constructors которая скорее всего приведёт к вызову "конструктора перемещения" в Вашей программе. Я говорю "скорее всего" т.к. компилятор всё равно может застесняться, поскольку Ваш конструктор перемещения не имеет при себе noexcept. Ведь кому хочется, чтобы во время перемещения было заброшено исключение?

#10 
UVV местный житель09.09.16 08:51
NEW 09.09.16 08:51 
в ответ anly 19.08.16 10:43

Конечно лажа, MSVS 2013 не имеет полной поддержки С++11 https://msdn.microsoft.com/en-us/library/hh567...

#11