Jak wymusić semantykę ruchu, gdy wektor rośnie?


93

Mam std::vectorobiekty określonej klasy A. Klasa jest nietrywialna i ma zdefiniowane konstruktory kopiujące i przenoszące.

std::vector<A>  myvec;

Jeśli wypełnię wektor Aobiektami (używając np. myvec.push_back(a)), Wektor będzie się powiększał, używając konstruktora kopiującego A( const A&)do tworzenia nowych kopii elementów w wektorze.

Czy mogę w jakiś sposób wymusić, że Azamiast tego używany jest konstruktor przenoszenia klasy ?


5
Możesz, używając implementacji wektorów obsługujących ruch.
— K-ballo

2
Czy możesz bardziej szczegółowo określić, jak to osiągnąć?
— Bertwim van Beest

1
Po prostu używasz implementacji wektorowej obsługującej ruch. Wygląda na to, że Twoja standardowa implementacja biblioteki (która tak przy okazji?) Nie jest świadoma ruchu. Możesz spróbować z kontenerami obsługującymi ruch z Boost.
— K-ballo

1
Cóż, używam gcc 4.5.1, który jest świadomy ruchu.
— Bertwim van Beest

W moim kodzie udało się uczynić konstruktor kopiujący prywatnym, mimo że konstruktor przenoszenia nie miał jawnego „noexcept”.
— Arne

Odpowiedzi:


129

Musisz poinformować C ++ (konkretnie std::vector), że twój konstruktor przenoszenia i destruktor nie rzucają, używając noexcept. Następnie konstruktor przenoszenia zostanie wywołany, gdy wektor wzrośnie.

Oto jak zadeklarować i zaimplementować konstruktor ruchu, który jest przestrzegany przez 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...
}

Jeśli konstruktor nie jest noexcept, std::vectornie można go używać, ponieważ wtedy nie będzie w stanie zapewnić gwarancje wyjątków wymaganych przez normy.

Aby uzyskać więcej informacji na temat tego, co zostało powiedziane w standardzie, przeczytaj semantykę ruchu i wyjątki w języku C ++

Podziękowania dla Bo, który zasugerował, że może to mieć związek z wyjątkami. Weź również pod uwagę rady Kerreka SB i użyj ich, emplace_backjeśli to możliwe. To może być szybsze (ale często nie jest), to może być jaśniejszy i bardziej zwarta, ale jest też kilka pułapek (zwłaszcza bez wyraźnych konstruktorów).

Edytuj , często domyślnym jest to, czego chcesz: przenieś wszystko, co można przenieść, skopiuj resztę. Aby wyraźnie o to poprosić, napisz

A(A && rhs) = default;

Robiąc to, otrzymasz noexcept, jeśli to możliwe: Czy domyślny konstruktor Move jest zdefiniowany jako noexcept?

Należy pamiętać, że wczesne wersje programu Visual Studio 2015 i starsze nie obsługiwały tego, mimo że obsługuje semantykę przenoszenia.


Z zainteresowaniem, jak ma impl „wie” czy value_type„y ruch konstruktor jest noexcept? Być może język ogranicza zestaw kandydatów wywołania funkcji, gdy zakres wywołania jest również noexceptfunkcją?
— Wyścigi lekkości na orbicie

1
@LightnessRacesinOrbit Zakładam, że robi coś takiego jak en.cppreference.com/w/cpp/types/is_move_constructible . Może istnieć tylko jeden konstruktor przenoszenia, więc powinien być jasno zdefiniowany w deklaracji.
— Johan Lundberg,

@LightnessRacesinOrbit, od tego czasu dowiedziałem się, że nie ma (standardowego / użytecznego) sposobu, aby naprawdę wiedzieć, czy istnieje noexceptkonstruktor ruchu. is_nothrow_move_constructiblebędzie prawdziwe, jeśli istnieje nothrowkonstruktor kopiujący. Nie znam żadnego prawdziwego przypadku drogich nothrowkonstruktorów kopiujących, więc nie jest jasne, czy to naprawdę ma znaczenie.
— Johan Lundberg

Nie działa na mnie. Mój destruktor, konstruktor przenoszenia i funkcje przypisania przenoszenia są zaznaczone noexceptzarówno w nagłówku, jak i implementacji, a kiedy wykonuję push_back (std :; move), nadal wywołuje konstruktor kopiujący. Rwę sobie tutaj włosy.
— AlastairG

1
@Johan Znalazłem problem. Użyłem std::move()niewłaściwego push_back()połączenia. Jeden z tych momentów, kiedy tak bardzo szukasz problemu, że nie widzisz przed sobą oczywistego błędu. A potem była pora obiadowa i zapomniałem usunąć swój komentarz.
— AlastairG

17

Co ciekawe, wektor gcc 4.7.2 używa konstruktora przenoszenia tylko wtedy, gdy zarówno konstruktor przenoszenia, jak i destruktor są noexcept. Prosty przykład:

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();
}

To daje oczekiwane:

move
move
move

Jednak kiedy usuwam noexceptz ~foo(), wynik jest inny:

copy
copy
copy

Myślę, że to również odpowiada na to pytanie .


Wydaje mi się, że inne odpowiedzi mówią tylko o konstruktorze ruchu, a nie o destruktorze, który nie może być wyjątkiem.
— Nikola Benes

Cóż, powinno być, ale jak się okazuje, w gcc 4.7.2 tak nie było. Więc ten problem był w rzeczywistości specyficzny dla gcc. Powinien jednak zostać naprawiony w gcc 4.8.0. Zobacz powiązane pytanie dotyczące przepełnienia stosu .
— Nikola Benes

-1

Wydaje się, że jedynym sposobem (dla C ++ 17 i wczesnych), aby wymusić std::vectorużycie semantyki przenoszenia przy realokacji, jest usunięcie konstruktora kopiującego :). W ten sposób użyje twoich konstruktorów przenoszenia lub umrze próbując w czasie kompilacji :).

Istnieje wiele reguł, w których std::vectorNIE WOLNO używać konstruktora przenoszenia przy ponownej alokacji, ale nic o tym, gdzie MUSI go UŻYWAĆ .

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;   
};

Relacja na żywo

lub

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 {};   
};

Kod na żywo

Twoja Tklasa musi mieć noexceptkonstruktor przenoszenia / operator przypisania i noexceptdestruktor. W przeciwnym razie pojawi się błąd kompilacji.

std::vector<move_only<MyClass>> vec;

1
Nie jest konieczne usuwanie konstruktora kopiującego. Jeśli konstruktor przenoszenia jest noexcept, zostanie użyty.
— balki

@balki MOŻE być używane. Standard nie WYMAGA tego teraz. Oto dyskusja groups.google.com/a/isocpp.org/forum/...
— tower120
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.