Як застосувати семантику переміщення, коли вектор росте?


93

У мене є std::vectorоб'єкти певного класу A. Клас нетривіальний і має конструктори копіювання та конструктори переміщення.

std::vector<A>  myvec;

Якщо я заповню вектор A об'єктами (використовуючи, наприклад myvec.push_back(a)), вектор збільшиться в розмірі, використовуючи конструктор копіюванняA( const A&) для створення нових копій елементів у векторі.

Чи можу я якось Aдомогтися того, що замість цього використовується конструктор переміщення класу ?


5
Ви можете, використовуючи реалізацію вектора, що усвідомлює переміщення.
K-ballo

2
Будь ласка, будьте дещо конкретнішими, як цього досягти?
Бертвім ван Беест

1
Ви просто використовуєте векторну реалізацію, що усвідомлює переміщення. Схоже, ваша стандартна бібліотечна реалізація (а це, до речі?) Не помічає руху. Ви можете спробувати з контейнерами Boost, які підтримують переміщення.
K-ballo

1
Ну, я використовую gcc 4.5.1, який є обізнаним.
Бертвім ван Беест

У моєму коді це вдалося зробити конструктором копіювання приватним, хоча в конструкторі переміщення не було явного "noexcept".
Арне

Відповіді:


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 semantics and Exceptions

Похвала Бо, який натякнув, що, можливо, це пов’язано з винятками. Також враховуйте поради Kerrek SB і використовуйте їх, emplace_backколи це можливо. Це може бути швидше (але часто ні), може бути чіткішим і компактнішим, але є й деякі підводні камені (особливо з не явними конструкторами).

Редагуйте , часто за замовчуванням ви хочете: перемістіть все, що можна перемістити, решту скопіюйте. Щоб прямо просити про це, пишіть

A(A && rhs) = default;

Роблячи це, ви отримаєте за винятком випадків, коли це можливо: чи визначено конструктор 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

Я думаю, це також відповідає на це питання .


Мені здається, що інші відповіді говорять лише про конструктор переміщення, а не про те, що деструктор не повинен бути за винятком.
Нікола Бенеш

Ну, так і повинно бути, але, як виявляється, у 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;

1
Не потрібно видаляти конструктор копіювання. Якщо конструктор переміщення не є окрім, він буде використаний.
balki

@balki Це МОЖЕ бути використано. Стандарт не вимагає цього зараз. Ось дискусія groups.google.com/a/isocpp.org/forum/…
tower120
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.