В основном я делаю следующее. В моем классе D есть три конструктора (по умолчанию, перемещение, копирование) и два перегруженных оператора присваивания (перемещение и копирование). Я ожидал, что любое создание объекта типа D вызовет хотя бы один из пяти.
Однако создание объекта D "d4" следующим образом не вызывает ни одного из них:
D d4( foo() ); // foo returns a D
Вот код, который воспроизводит проблему, о которой я думал:
#include <iostream>
#include <vector>
#include <cassert>
using std::cout;
using std::endl;
class D {
public:
D()
{ cout << "D default"<<endl;}
D(const D& d)
{
cout << "D copy" << endl;
}
D(D&& d)
{
cout << "D rval" << endl;
assert(0);
}
D& operator=(D&& d)
{
cout << "D mv assign" << endl;
return *this;
}
D& operator=(const D& d)
{
cout << "D copy assign" << endl;
return *this;
}
volatile int v;
};
// return
D foo()
{
D res;
cout <<"returning a non const D" << endl;
return res;
}
int main()
{
D d4(foo());
return 0;
}
По сути, я предположил, что D(D&& d) будет вызываться для создания d4, поскольку foo() возвращает временное значение, адрес которого не может быть взят. На самом деле это было верно только тогда, когда оптимизация возвращаемого значения была отключена с помощью -fno-elide-constructors.
Однако, если он не был указан, оптимизация RV по умолчанию включена даже при -O0. Затем то, что я увидел, выглядит следующим образом:
D default
returning a non const D
Все, что я видел из стандартного вывода, пришло из foo(). Само создание d4 мне ничего не дало. Это отличается от того, что я ожидал.
Я ожидал следующего. Пространство памяти для возвращаемого значения выделяется в стеке вызывающего объекта, а не в стеке вызываемого объекта. Конструктор по умолчанию вызывается, чтобы коснуться области памяти. Никакого копирования из стека вызываемого объекта в стек вызывающего не произойдет. После этого в стеке вызывающего объекта выделяется еще одно пространство памяти. Поскольку возвращаемое значение является значением r, конструктор перемещения вызывается для записи чего-либо в «другое пространство памяти в стеке вызывающего объекта».
Я знаю, что это может потребовать избыточного пространства памяти. Однако, особенно в моем примере, конструктор перемещения умрет с помощью assert(0). Какой бы ни был конструктор, но он позволит программе продолжить работу. В результате оптимизация возвращаемого значения повлияла на производительность программы.
Это ожидается? Если да, то в чем причина? Я тестировал как g++-7.3.0, так и clang++-5.0.1. Они были такими же.