Bagaimana cara menerapkan semantik bergerak saat vektor tumbuh?

93

Saya memiliki std::vectorobjek dari kelas tertentu A. Kelas tidak sepele dan memiliki konstruktor salinan dan konstruktor bergerak yang ditentukan.

std::vector<A>  myvec;

Jika saya mengisi vektor dengan Aobjek (menggunakan contoh myvec.push_back(a)), ukuran vektor akan bertambah, menggunakan konstruktor salinan A( const A&)untuk membuat salinan baru dari elemen dalam vektor.

Dapatkah saya entah bagaimana memaksakan bahwa konstruktor pemindahan kelas Amemohon digunakan sebagai gantinya?

Bertwim van Beest
sumber
5
Anda bisa, dengan menggunakan implementasi vektor sadar-bergerak.
K-ballo
2
Bisakah Anda sedikit lebih spesifik bagaimana mencapai ini?
Bertwim van Beest
1
Anda cukup menggunakan implementasi vektor sadar-bergerak. Sepertinya implementasi pustaka standar Anda (yang mana btw?) Tidak peka-gerakan. Anda dapat mencoba dengan kontainer yang peka bergerak dari Boost.
K-ballo
1
Ya, saya menggunakan gcc 4.5.1, yang merupakan gerakan sadar.
Bertwim van Beest
Dalam kode saya, itu berhasil membuat salinan konstruktor pribadi, meskipun konstruktor pemindahan tidak memiliki "noexcept" eksplisit.
Arne

Jawaban:

129

Anda perlu memberi tahu C ++ (secara khusus std::vector) bahwa konstruktor dan destruktor pemindahan Anda tidak melempar, menggunakan noexcept. Kemudian konstruktor bergerak akan dipanggil ketika vektor tumbuh.

Ini adalah cara mendeklarasikan dan mengimplementasikan konstruktor bergerak yang diikuti oleh 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...
}

Jika konstruktor tidak noexcept, tidak std::vectordapat menggunakannya, karena itu tidak dapat memastikan jaminan pengecualian yang diminta oleh standar.

Untuk mengetahui lebih lanjut tentang apa yang diucapkan dalam standar, baca C ++ Move semantik dan Pengecualian

Penghargaan untuk Bo yang mengisyaratkan bahwa itu mungkin ada hubungannya dengan pengecualian. Pertimbangkan juga saran Kerrek SB dan gunakan emplace_backjika memungkinkan. Ini bisa lebih cepat (tetapi seringkali tidak), bisa lebih jelas dan lebih kompak, tetapi ada juga beberapa kendala (terutama dengan konstruktor non-eksplisit).

Edit , sering kali defaultnya adalah yang Anda inginkan: pindahkan semua yang dapat dipindahkan, salin sisanya. Untuk secara eksplisit memintanya, tulis

A(A && rhs) = default;

Melakukan itu, Anda akan mendapatkan noexcept jika memungkinkan: Apakah default Move constructor didefinisikan sebagai noexcept?

Perhatikan bahwa versi awal Visual Studio 2015 dan yang lebih lama tidak mendukungnya, meskipun mendukung semantik bergerak.

Johan Lundberg
sumber
Dari bunga, bagaimana tidak impl "tahu" apakah value_type's bergerak ctor adalah noexcept? Mungkin bahasa membatasi set kandidat panggilan fungsi ketika cakupan panggilan juga merupakan noexceptfungsi?
Balapan Ringan di Orbit
1
@LightnessRacesinOrbit Saya menganggap itu hanya melakukan sesuatu seperti en.cppreference.com/w/cpp/types/is_move_constructible . Hanya ada satu konstruktor gerakan sehingga harus didefinisikan dengan jelas oleh deklarasi.
Johan Lundberg
@LightnessRacesinOrbit, saya telah belajar bahwa tidak ada cara (standar / berguna) untuk benar-benar mengetahui apakah ada noexceptkonstruktor pemindahan. is_nothrow_move_constructibleakan benar jika ada nothrowkonstruktor salinan. Saya tidak mengetahui kasus nyata dari pembuat nothrowsalinan mahal jadi tidak jelas apakah itu benar-benar penting.
Johan Lundberg
Tidak berhasil untuk saya. Destruktor saya, pindahkan konstruktor, dan pindahkan fungsi tugas semuanya ditandai noexceptdi header dan implementasi, dan ketika saya melakukan push_back (std:; move) itu masih memanggil konstruktor salinan. Saya merobek rambut saya di sini.
AlastairG
1
@Johan saya menemukan masalahnya. Saya menggunakan panggilan yang std::move()salah push_back(). Suatu saat ketika Anda mencari begitu keras untuk suatu masalah sehingga Anda tidak melihat kesalahan yang jelas tepat di depan Anda. Dan kemudian saat makan siang dan saya lupa menghapus komentar saya.
AlastairG
17

Menariknya, vektor gcc 4.7.2 hanya menggunakan konstruktor pemindah jika konstruktor pemindah dan destruktornya sama-sama noexcept. Contoh sederhana:

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

Ini menghasilkan yang diharapkan:

move
move
move

Namun, saat saya menghapus noexceptdari ~foo(), hasilnya berbeda:

copy
copy
copy

Saya rasa ini juga menjawab pertanyaan ini .

Nikola Benes
sumber
Bagi saya, jawaban yang lain hanya berbicara tentang konstruktor pemindahan, bukan tentang destruktor yang harus ada kecuali.
Nikola Benes
Memang seharusnya begitu, tetapi ternyata, di gcc 4.7.2 tidak. Jadi, masalah ini sebenarnya khusus untuk gcc. Namun, ini harus diperbaiki di gcc 4.8.0. Lihat pertanyaan stackoverflow terkait .
Nikola Benes
-1

Tampaknya, satu-satunya cara (untuk C ++ 17 dan awal), untuk menerapkan std::vectorsemantik pemindahan penggunaan pada realokasi adalah menghapus konstruktor salinan :). Dengan cara ini, ia akan menggunakan konstruktor pemindahan Anda atau mati saat mencoba, pada waktu kompilasi :).

Ada banyak aturan di mana std::vectorTIDAK HARUS menggunakan konstruktor bergerak saat realokasi, tetapi tidak ada aturan di mana ia HARUS MENGGUNAKANnya .

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

Hidup

atau

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

Kode langsung

TKelas Anda harus memiliki noexceptoperator pemindah / penugasan dan noexceptpenghancur. Jika tidak, Anda akan mendapatkan kesalahan kompilasi.

std::vector<move_only<MyClass>> vec;
tower120
sumber
1
Tidak perlu menghapus konstruktor salinan. Jika konstruktor pindahkan tidak terkecuali, itu akan digunakan.
balki
@bali Ini MUNGKIN digunakan. Standar tidak MEMERLUKAN ini sekarang. Ini adalah diskusi groups.google.com/a/isocpp.org/forum/…
tower120