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

8

Другие решения

Контейнеры попробуйте очень трудно убедиться, что они остаются пригодными для использования в случае возникновения исключения. Как часть этого, они будут использовать только std::move внутренне, если конструктор перемещения вашего класса безопасен для исключений. Если это не так (или не может сказать), он будет копировать просто для безопасности.

Правильные операции перемещения

Foo(Foo&&) noexcept {cout << "move ctor" << endl;}
Foo& operator=(Foo&&) noexcept {cout << "move assn" << endl;}
3

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector