С++ 11 move constructor не вызывается, предпочитаемый конструктор по умолчанию - программирование

С++ 11 move constructor не вызывается, предпочитаемый конструктор по умолчанию

Предположим, что мы имеем этот класс:

class X {
public:
    explicit X (char* c) { cout<<"ctor"<<endl; init(c); };
    X (X& lv)  { cout<<"copy"<<endl;  init(lv.c_); };
    X (X&& rv) { cout<<"move"<<endl;  c_ = rv.c_; rv.c_ = nullptr; };

    const char* c() { return c_; };

private:
    void init(char *c) { c_ = new char[strlen(c)+1]; strcpy(c_, c); };
    char* c_;

};

и использование этого примера:

X x("test");
cout << x.c() << endl;
X y(x);
cout << y.c() << endl;
X z( X("test") );
cout << z.c() << endl;

Вывод:

ctor
test
copy
test
ctor   <-- why not move?
test

Я использую VS2010 с настройками по умолчанию. Я ожидаю, что последний объект (z) будет построен по ходу движения, но это не так! Если я использую X z( move(X("test")) );, то последние строки вывода ctor move test, как я и ожидал. Это случай (N) RVO?

Q: следует ли вызывать move-ctor в соответствии со стандартом? Если да, то почему он не называется?

4b9b3361

Ответ 1

То, что вы видите, это copy elision, что позволяет компилятору напрямую создать временную цель, которую нужно скопировать/переместить и, таким образом, удалить копию (или перемещать) конструктор/деструктор. Ситуации, в которых компилятору разрешено применять копирование, указаны в §12.8.32 стандарта С++ 11:

Когда выполняются определенные критерии, реализация может пропустить копирование/перемещение объекта класса, даже если копирование/перемещение конструктор и/или деструктор для объекта имеют побочные эффекты. В таком случаев, реализация рассматривает источник и цель опущенных копировать/перемещать как просто два разных способа обращения к тот же объект, и уничтожение этого объекта происходит на времена, когда два объекта были бы уничтожены без оптимизация. Это разрешение копирования/перемещения операции, называемые копией, разрешены в следующих обстоятельства (которые могут быть объединены для устранения нескольких копий):

  • в операторе return в функции с типом возвращаемого класса, когда выражение является именем энергонезависимого автоматического объекта с помощью тот же самый cv-неквалифицированный тип, как возвращаемый тип функции,
    операция копирования/перемещения может быть опущена путем создания автоматического объект непосредственно в возвращаемое значение функций
  • в выражении throw, когда операндом является имя энергонезависимого автоматического объекта, объем которого не выходит за пределы конец самого внутреннего охватывающего блока try-block (если таковой имеется), копировать/перемещать операцию из операнда в объект исключения (15.1) может быть опущено путем создания автоматического объекта непосредственно в объект исключения
  • когда объект временного класса, который не был привязан к ссылке (12.2), будет скопирован/перенесен в объект класса, если он такой же cv-неквалифицированный тип, операция копирования/перемещения может быть опущена построение временного объекта непосредственно в цель omitted copy/move
  • когда объявление исключения обработчика исключений (статья 15) объявляет объект того же типа (за исключением cv-qualification) как
    объект исключения (15.1), операция копирования/перемещения может быть опущена
    bytreating the exception-declaration как псевдоним для исключения
    если значение программы не изменится, за исключением выполнение конструкторов и деструкторов для объекта, объявленного с помощью объявление исключения.

Ответ 2

Вывод ctor, который вы получаете в своей третьей строке кода, предназначен для построения временного объекта. После этого, действительно, временное перемещается в новую переменную z. В такой ситуации компилятор может выбрать вариант копирования/перемещения, и, похоже, это то, что он сделал.

Стандарт утверждает:

(§12.8/31) Когда определенные критерии выполнены, реализации разрешено опускать конструкцию копирования/перемещения объекта класса, даже если конструктор copy/move и/или деструктор для объекта имеют побочные эффекты. [...] Это разрешение операций копирования/перемещения, называемое копированием, разрешено в следующих случаях (которые могут быть объединены для устранения нескольких копий):
[...]
 - когда объект временного класса, который не был привязан к ссылке (12.2), будет скопирован/перемещен в объект класса с тем же самым cv-неквалифицированным типом, операция копирования/перемещения может быть опущена путем создания временного объекта непосредственно в цель пропущенной копии/перемещение
[...]

Одним из важных условий является то, что исходный объект и пункт назначения имеют один и тот же тип (кроме cv-квалификации, то есть такие вещи, как const).

Следовательно, одним из способов принудительного вызова конструктора перемещения является объединение инициализации объекта с неявным преобразованием типа:

#include <iostream>

struct B
{};

struct A
{
  A() {}
  A(A&& a) {
    std::cout << "move" << std::endl;
  }
  A(B&& b) {
    std::cout << "move from B" << std::endl;
  }
};


int main()
{
  A a1 = A(); // move elided
  A a2 = B(); // move not elided because of type conversion
  return 0;
}

Ответ 3

Вы вызываете конструктор X's char* конструктор X("test") явно.

Поэтому печатается ctor