Как обеспечить соблюдение семантики перемещения при росте вектора?

93

У меня есть std::vectorобъекты определенного класса A. Класс нетривиален и имеет конструкторы копирования и перемещение конструктор определены.

std::vector<A>  myvec;

Если я заполню вектор Aобъектами (например, используя myvec.push_back(a)), вектор будет увеличиваться в размере, используя конструктор копирования A( const A&)для создания новых копий элементов в векторе.

Могу ли я каким-то образом добиться, чтобы Aвместо этого использовался конструктор перемещения класса ?

Бертвим ван Бест
источник
5
Вы можете это сделать, используя векторную реализацию с поддержкой перемещения.
K-bal
2
Не могли бы вы уточнить, как этого добиться?
Bertwim van Beest
1
Вы просто используете векторную реализацию с поддержкой перемещения. Похоже, реализация вашей стандартной библиотеки (кстати, это?) Не поддерживает перемещение. Вы можете попробовать использовать контейнеры с поддержкой перемещения от Boost.
K-bal
1
Ну, я использую gcc 4.5.1, который поддерживает перемещение.
Bertwim van Beest
В моем коде получилось сделать конструктор копирования закрытым, хотя конструктор перемещения не имел явного "noexcept".
Arne

Ответы:

129

Вам нужно сообщить C ++ (в частности std::vector), что ваш конструктор перемещения и деструктор не бросают, используя noexcept. Затем при увеличении вектора будет вызываться конструктор перемещения.

Вот как объявить и реализовать конструктор перемещения, который соблюдается std::vector:

A(A && rhs) noexcept { 
  std::cout << "i am the move constr" <<std::endl;
  ... some code doing the move ...  
  m_value=std::move(rhs.m_value) ; // etc...
}

Если конструктора нет noexcept, std::vectorего нельзя использовать, поскольку он не может гарантировать гарантии исключения, требуемые стандартом.

Чтобы узнать больше о том, что сказано в стандарте, прочтите Семантику и исключения C ++ Move.

Благодарим Бо, который намекнул, что это, возможно, связано с исключениями. Также примите во внимание совет Керрека С.Б. и используйте его, emplace_backкогда это возможно. Он может быть быстрее (но часто нет), он может быть более понятным и компактным, но есть и некоторые подводные камни (особенно с неявными конструкторами).

Редактировать , часто по умолчанию это то, что вы хотите: переместите все, что можно переместить, скопируйте остальное. Чтобы явно попросить об этом, напишите

A(A && rhs) = default;

При этом вы получите noexcept, когда это возможно: определен ли конструктор Move по умолчанию как noexcept?

Обратите внимание, что ранние версии Visual Studio 2015 и более ранние не поддерживали это, хотя и поддерживали семантику перемещения.

Йохан Лундберг
источник
Из интереса, как это осущ «знает» ли value_type«s движение к т е р это noexcept? Возможно, язык ограничивает набор кандидатов на вызов функции, если область вызова также является noexceptфункцией?
Гонки легкости на орбите
1
@LightnessRacesinOrbit Я предполагаю, что он просто делает что-то вроде en.cppreference.com/w/cpp/types/is_move_constructible . Конструктор перемещения может быть только один, поэтому он должен быть четко определен в объявлении.
Йохан Лундберг
@LightnessRacesinOrbit, с тех пор я узнал, что не существует (стандартного / полезного) способа узнать, есть ли noexceptконструктор перемещения. is_nothrow_move_constructibleбудет истинным, если есть nothrowконструктор копирования. Я не знаю ни одного реального случая использования дорогостоящих nothrowконструкторов копирования, поэтому неясно, действительно ли это имеет значение.
Йохан Лундберг
У меня не работает. Мой деструктор, конструктор перемещения и функции назначения перемещения отмечены noexceptкак в заголовке, так и в реализации, и когда я выполняю push_back (std:; move), он по-прежнему вызывает конструктор копирования. Я рву волосы здесь.
AlastairG
1
@Johan, я нашел проблему. Я использовал std::move()неправильный push_back()вызов. Один из тех случаев, когда вы так усердно ищете проблему, что не видите очевидную ошибку прямо перед собой. А потом наступил обеденный перерыв, и я забыл удалить свой комментарий.
AlastairG
17

Интересно, что вектор gcc 4.7.2 использует конструктор перемещения, только если он и конструктор перемещения, и деструктор noexcept. Простой пример:

struct foo {
    foo() {}
    foo( const foo & ) noexcept { std::cout << "copy\n"; }
    foo( foo && ) noexcept { std::cout << "move\n"; }
    ~foo() noexcept {}
};

int main() {
    std::vector< foo > v;
    for ( int i = 0; i < 3; ++i ) v.emplace_back();
}

Это выводит ожидаемый:

move
move
move

Однако, когда я удаляю noexceptиз ~foo(), результат другой:

copy
copy
copy

Думаю, это тоже отвечает на этот вопрос .

Никола Бенеш
источник
Мне кажется, что другие ответы говорят только о конструкторе перемещения, а не о том, что деструктор должен быть noexcept.
Никола Бенеш
Так и должно быть, но, как оказалось, в gcc 4.7.2 этого не было. Таким образом, эта проблема была на самом деле специфической для gcc. Однако это должно быть исправлено в gcc 4.8.0. См. Соответствующий вопрос о stackoverflow .
Никола Бенеш
-1

Похоже, что единственный способ (для C ++ 17 и более ранних версий) принудительно std::vectorиспользовать семантику перемещения при перераспределении - это удалить конструктор копирования :). Таким образом, он будет использовать ваши конструкторы перемещения или умереть, пытаясь во время компиляции :).

Есть много правил, по которым std::vectorНЕ ДОЛЖНО использовать конструктор перемещения при перераспределении, но ничего не говорится о том, где он ДОЛЖЕН его ИСПОЛЬЗОВАТЬ .

template<class T>
class move_only : public T{
public:
   move_only(){}
   move_only(const move_only&) = delete;
   move_only(move_only&&) noexcept {};
   ~move_only() noexcept {};

   using T::T;   
};

Прямой эфир

или

template<class T>
struct move_only{
   T value;

   template<class Arg, class ...Args, typename = std::enable_if_t<
            !std::is_same_v<move_only<T>&&, Arg >
            && !std::is_same_v<const move_only<T>&, Arg >
    >>
   move_only(Arg&& arg, Args&&... args)
      :value(std::forward<Arg>(arg), std::forward<Args>(args)...)
   {}

   move_only(){}
   move_only(const move_only&) = delete;   
   move_only(move_only&& other) noexcept : value(std::move(other.value)) {};    
   ~move_only() noexcept {};   
};

Живой код

В вашем Tклассе должен быть noexceptконструктор перемещения / оператор присваивания и noexceptдеструктор. В противном случае вы получите ошибку компиляции.

std::vector<move_only<MyClass>> vec;
башня120
источник
1
Конструктор копирования не нужно удалять. Если конструктор перемещения не имеет значения, он будет использован.
balki
@balki МОЖНО использовать. Стандарт сейчас этого НЕ ТРЕБУЕТ. Вот обсуждение groups.google.com/a/isocpp.org/forum/…
tower120,