std::vector инициализация перемещения / копирования конструктора элемента
У меня есть этот кусок кода:
#include <iostream>
#include <vector>
using namespace std;
class Foo{
public:
Foo() noexcept {cout << "ctor" << endl;}
Foo(const Foo&) noexcept {cout << "copy ctor" << endl;}
Foo(Foo&&) noexcept {cout << "move ctor" << endl;}
Foo& operator=(Foo&&) noexcept {cout << "move assn" << endl; return *this;}
Foo& operator=(const Foo&) noexcept {cout << "copy assn" << endl; return *this;}
~Foo() noexcept {cout << "dtor" << endl;}
};
int main()
{
Foo foo;
vector<Foo> v;
v.push_back(std::move(foo));
// comment the above 2 lines and replace by
// vector<Foo> v{std::move(foo)};
}
Результат - то, что я ожидаю (скомпилировано с g++ -std=c++11 --no-elide-constructors
Тот же вывод без флага)
ctor
move ctor
dtor
dtor
Теперь вместо использования push_back
инициализировать непосредственно вектор v
как
vector<Foo> v{std::move(foo)};
Я не понимаю, почему я получаю выводы:
1) (без --no-elide-constructors
)
ctor
move ctor
copy ctor
dtor
dtor
dtor
2) (с --no-elide-constructors
)
ctor
move ctor
move ctor
copy ctor
dtor
dtor
dtor
dtor
В первом случае, почему вызывается копия ctor? И во втором случае, когда компилятор не выполняет elision, я абсолютно не понимаю, почему ctor-код запускается дважды. Есть идеи?
2 ответа
vector<Foo> v{std::move(foo)};
Здесь вы вызываете конструктор вектора, который принимает std::initializer_list
, Список инициализатора позволяет только const
доступ к его элементам, поэтому vector
придется скопировать каждый элемент из initializer_list
в собственное хранилище. Вот что вызывает вызов конструктора копирования.
Из §8.5.4/5 [dcl.init.list]
Объект типа
std::initializer_list<E>
построен из списка инициализатора, как если бы реализация выделяла временный массивN
элементы типаconst E
, гдеN
это количество элементов в списке инициализатора.
Что касается дополнительного вызова конструктора перемещения с -fno-elide-constructors
Это обсуждалось в другом ответе пару дней назад. Кажется, что g++ использует очень буквальный подход к реализации примера initializer_list
показано в стандарте в том же разделе, который я цитировал выше.
Тот же пример, когда он скомпилирован с использованием clang, не вызывает дополнительный вызов конструктора move.
Контейнеры очень стараются, чтобы они оставались пригодными для использования в случае возникновения исключения. Как часть этого, они будут использовать только std::move
внутренне, если конструктор перемещения вашего класса безопасен для исключений. Если это не так (или не может сказать), оно будет скопировано просто для безопасности.
Правильные операции перемещения
Foo(Foo&&) noexcept {cout << "move ctor" << endl;}
Foo& operator=(Foo&&) noexcept {cout << "move assn" << endl;}