Могу ли я пометить операцию перемещения классов noкроме тех случаев, когда она содержит стандартный контейнер?

Идиоматический способ реализации операций перемещения в классах со стандартным элементом-контейнером не может быть noexcept и, следовательно, не может быть перемещен с помощью таких операций, как vector.push_back(). Или я ошибаюсь?

Чтобы получить скорость от

vector<Elem> data;
// ...
data.push_back( elem );

Мы рекомендуем выполнять операции перемещения noexcept -- чтобы во время изменения размера векторов библиотека могла безопасно перемещать элементы в перераспределенное хранилище.

class Elem {
    // ...
    Elem(Elem&&) noexcept;            // noexcept important for move
    Elem& operator=(Elem&&) noexcept; // noexcept important for move
};

Пока все хорошо, теперь мои elem можно отталкивать гораздо быстрее.

Но: если я добавлю контейнер в качестве члена, может ли мой класс по-прежнему быть помечен как noexcept-move? Все стандартные контейнеры не имеют ход noexcept!

class Stuff {
    vector<int> bulk;
    // ...
    Stuff(Stuff&& o)  // !!! no noexcept because of vector-move  
      : bulk(move(o.bulk))
      {}
    Stuff& operator=(Stuff&&) // !!! no noexcept...
      { /* appropriate implementation */ }
};

Это также означает, что мы также не можем полагаться на сгенерированные компилятором операции перемещения, верно? Следующий полный класс также не будет иметь noexcept-операций перемещения и, следовательно, не будет «быстрым», верно?

struct Holder {
    vector<int> bulk;
};

Возможно, vector<int> слишком просто перемещать, но как насчет vector<Elem>?

Это имело бы серьезные последствия для всех структур данных с контейнерами в качестве членов.


person towi    schedule 23.01.2014    source источник
comment
Часть проблемы заключается в неизвестных признаках распределителя. Можно подумать, что std::vector::swap() может быть и noexcept.   -  person woolstar    schedule 24.01.2014
comment
Несмотря на то, что функции-члены стандартного контейнера не имеют обязательной спецификации noexcept, они имеют гарантии исключения. Если в этих гарантиях говорится, что операция не приводит к броску, вы можете безопасно сделать свой собственный ход без исключений.   -  person dyp    schedule 24.01.2014


Ответы (2)


Я чувствую твою боль, правда.

Некоторые реализации std:: будут помечать элементы перемещения контейнеров как noexcept, по крайней мере, в зависимости от свойств распределителя, как расширение. Вы можете адаптировать свой код для автоматического использования этих расширений, например:

class Stuff {
    std::vector<int> bulk;
    // ...
public:
    Stuff(Stuff&& o)
      noexcept(std::is_nothrow_move_constructible<std::vector<int>>::value)
      : bulk(std::move(o.bulk))
      {}
    Stuff& operator=(Stuff&&)
      noexcept(std::is_nothrow_move_assignable<std::vector<int>>::value)
      { /* appropriate implementation */ }
};

И вы даже можете проверить, есть ли в вашем типе элементы noexcept move:

static_assert(std::is_nothrow_move_constructible<Stuff>::value,
                     "I hope Stuff has noexcept move members");
static_assert(std::is_nothrow_move_assignable<Stuff>::value,
                     "I hope Stuff has noexcept move members");

libc++, в частности, не имеет элементов перемещения noexcept для всех своих контейнеров, когда это позволяет распределитель, и std::allocator всегда позволяет членам перемещения контейнера быть noexcept .

person Howard Hinnant    schedule 23.01.2014
comment
Хорошо спасибо. А что насчет того, что компилятор генерирует для struct Holder;? Я полагаю, что у этого класса будет неэффективный ход, т.е. копирующий? Я предполагаю, что компилятор не будет включать условное noexcept. (И если бы я продолжил шутку Скотта, я бы спросил, есть ли загружаемый бинарный файл для Windows, пока, но не буду ;-) - person towi; 24.01.2014
comment
На самом деле спецификация noexcept для Holder будет точно такой же, как я явно рекомендовал вам сделать для Stuff. В самом деле, вы также можете просто использовать = default. И вы можете протестировать спецификацию noexcept Holder точно так же, как я показал для Stuff. В частности, если конструктором перемещения для vector<int> является noexcept, то неявный конструктор перемещения для Holder также будет noexcept. - person Howard Hinnant; 24.01.2014
comment
Мне полегчало. Насколько я понимаю, это то, что §12.9 содержит множество слов. - person towi; 24.01.2014
comment
@HowardHinnant Тест может немного вводить в заблуждение, поскольку он также может перехватывать noexcept конструкторы копирования; см. комментарии на странице stackoverflow.com/a/18654089/1046167. - person Louis Semprini; 24.09.2020
comment
Если конструктор копирования должен использоваться для перемещения, черты is_nothrow_move_ по-прежнему являются правильным инструментом для задания вопроса (по замыслу). - person Howard Hinnant; 25.09.2020
comment
Та же проблема смутила комитет десять лет назад и была исправлена ​​путем переименования признаков в этой статье: open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3142.html - person Howard Hinnant; 25.09.2020

Это зависит от распределителя, который использует ваш стандартный контейнер. Распределитель по умолчанию std::allocator<T> гарантированно не выбрасывает при копировании (и не имеет конструктора перемещения), что, в свою очередь, означает, что контейнер не будет выбрасывать при перемещении.

Одна интересная особенность noexcept по сравнению с устаревшим throw() заключается в том, что вы можете использовать выражение, которое оценивается во время компиляции. Хотя точное условие для проверки может быть не тривиальным...

person David Rodríguez - dribeas    schedule 23.01.2014