Mengapa kita bisa menggunakan `std :: move` pada objek` const`?

113

Di C ++ 11, kita bisa menulis kode ini:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

ketika saya panggil std::move, artinya saya ingin memindahkan objek, yaitu saya akan mengubah objek. Untuk memindahkan constobjek tidak masuk akal, jadi mengapa std::movetidak membatasi perilaku ini? Itu akan menjadi jebakan di masa depan, bukan?

Di sini jebakan berarti seperti yang disebutkan Brandon dalam komentar:

"Saya pikir dia bersungguh-sungguh" menjebak "dia dengan licik karena jika dia tidak menyadarinya, dia berakhir dengan salinan yang bukan itu yang dia inginkan."

Dalam buku 'Effective Modern C ++' karya Scott Meyers, ia memberi contoh:

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

Jika std::movedilarang beroperasi pada suatu constobjek, kita bisa dengan mudah mengetahui bugnya, bukan?

camino
sumber
2
Tapi coba pindahkan. Cobalah untuk mengubah keadaannya. std::movedengan sendirinya tidak melakukan apa pun pada objek. Orang bisa membantah bahwa std::movenama itu buruk.
juanchopanza
3
Itu tidak benar-benar menggerakkan apapun. Semua itu dilemparkan ke referensi rvalue. coba CAT cat2 = std::move(cat);, dengan asumsi CATmendukung tugas-pindah biasa.
WhozCraig
11
std::movehanya gips, tidak benar-benar menggerakkan apa pun
Red Alert
2
@WhozCraig: Hati-hati, karena kode yang Anda posting dikompilasi dan dijalankan tanpa peringatan, membuatnya agak menyesatkan.
Mooing Duck
1
@MooingDuck Tidak pernah mengatakan itu tidak akan dikompilasi. ini bekerja hanya karena copy-ctor default diaktifkan. Hentikan itu dan roda akan jatuh.
WhozCraig

Jawaban:

55
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

di sini kita melihat penggunaan std::moveon a T const. Ini mengembalikan a T const&&. Kami memiliki konstruktor bergerak strangeyang membutuhkan tipe ini.

Dan itu disebut.

Memang benar bahwa jenis aneh ini lebih jarang daripada bug yang akan diperbaiki proposal Anda.

Tapi, di sisi lain, yang sudah ada std::movebekerja lebih baik dalam kode generik, di mana Anda tidak tahu apakah tipe yang Anda gunakan adalah a Tatau a T const.

Yakk - Adam Nevraumont
sumber
3
+1 untuk menjadi jawaban pertama yang benar-benar mencoba menjelaskan mengapa Anda ingin memanggil std::movesuatu constobjek.
Chris Drew
1 untuk menunjukkan pengambilan fungsi const T&&. Ini mengungkapkan "protokol API" dari jenis "Saya akan mengambil rvalue-ref tapi saya berjanji tidak akan memodifikasinya". Saya kira, selain saat menggunakan mutable, itu tidak biasa. Mungkin kasus penggunaan lain adalah untuk dapat digunakan forward_as_tupledi hampir semua hal dan kemudian menggunakannya.
F Pereira
107

Ada trik di sini yang Anda abaikan, yaitu std::move(cat) tidak benar-benar memindahkan apa pun . Ini hanya memberi tahu kompiler untuk mencoba bergerak. Namun, karena kelas Anda tidak memiliki konstruktor yang menerima a const CAT&&, ia akan menggunakan const CAT&konstruktor salinan implisit , dan menyalin dengan aman. Tidak ada bahaya, tidak ada jebakan. Jika konstruktor salinan dinonaktifkan karena alasan apa pun, Anda akan mendapatkan kesalahan kompiler.

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

mencetak COPY, tidak MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Perhatikan bahwa bug dalam kode yang Anda sebutkan adalah masalah kinerja , bukan masalah stabilitas , jadi bug seperti itu tidak akan pernah menyebabkan kerusakan. Ini hanya akan menggunakan salinan yang lebih lambat. Selain itu, bug seperti itu juga terjadi untuk objek non-const yang tidak memiliki konstruktor pemindah, jadi sekadar menambahkan constbeban berlebih tidak akan menangkap semuanya. Kita bisa memeriksa kemampuan untuk memindahkan konstruksi atau memindahkan assign dari tipe parameter, tapi itu akan mengganggu kode template generik yang seharusnya kembali ke copy konstruktor. Dan sih, mungkin seseorang ingin dapat membangun const CAT&&, dari siapa saya mengatakan dia tidak bisa?

Mooing Duck
sumber
Diangkat. Perlu dicatat bahwa penghapusan implisit dari copy-ctor berdasarkan definisi yang ditentukan pengguna dari konstruktor pemindah biasa atau operator penugasan juga akan menunjukkan hal ini melalui kompilasi rusak. Jawaban bagus.
WhozCraig
Juga perlu disebutkan bahwa copy-constructor yang membutuhkan non- constlvalue juga tidak akan membantu. [class.copy] §8: "Jika tidak, konstruktor salinan yang dideklarasikan secara implisit akan memiliki formulir X::X(X&)"
Deduplicator
9
Saya tidak berpikir yang dia maksud "jebakan" dalam istilah komputer / perakitan. Saya pikir dia bersungguh-sungguh "menjebak" dia secara licik karena jika dia tidak menyadarinya, dia berakhir dengan salinan yang bukan itu yang dia inginkan. Saya kira ..
Brandon
semoga Anda mendapatkan 1 suara positif lagi, dan saya mendapatkan 4 suara lagi, untuk lencana emas. ;)
Yakk - Adam Nevraumont
Tos di sampel kode itu. Mendapatkan intinya dengan sangat baik.
Brent Menulis Kode
20

Salah satu alasan mengapa sisa jawaban sejauh ini terabaikan adalah kemampuan untuk kode generik yang tangguh dalam menghadapi pemindahan. Sebagai contoh katakanlah saya ingin menulis fungsi umum yang memindahkan semua elemen dari satu jenis wadah untuk membuat jenis wadah lain dengan nilai yang sama:

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

Keren, sekarang saya bisa membuat file vector<string> dari a deque<string>dan setiap individu stringakan dipindahkan dalam prosesnya.

Tetapi bagaimana jika saya ingin pindah dari file map ?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

Jika std::movebersikeras pada non- constargumen, contoh di atas move_eachtidak akan dikompilasi karena mencoba untuk memindahkan a const int( key_typedari map). Tetapi kode ini tidak peduli jika tidak dapat memindahkan key_type. Ini ingin memindahkan mapped_type( std::string) untuk alasan kinerja.

Untuk contoh ini, dan banyak contoh lain seperti itu dalam pengkodean generik yang std::movemerupakan permintaan untuk pindah , bukan permintaan untuk pindah.

Howard Hinnant
sumber
2

Saya memiliki perhatian yang sama dengan OP.

std :: move tidak memindahkan objek, juga tidak menjamin objek dapat dipindahkan. Lalu kenapa disebut move?

Saya pikir menjadi tidak dapat dipindahkan dapat menjadi salah satu dari dua skenario berikut:

1. Jenis yang bergerak adalah const.

Alasan kami memiliki kata kunci const dalam bahasa ini adalah karena kami ingin kompiler mencegah perubahan apa pun pada objek yang didefinisikan menjadi const. Diberikan contoh dalam buku Scott Meyers:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     {  } // doesn't do what it seems to!    
     
    private:
     std::string value;
    };

Apa artinya secara harfiah? Pindahkan string const ke value member - setidaknya, itulah pemahaman saya sebelum saya membaca penjelasannya.

Jika bahasa bermaksud untuk tidak melakukan perpindahan atau tidak menjamin perpindahan dapat diterapkan saat std :: move () dipanggil, maka secara harfiah menyesatkan saat menggunakan word move.

Jika bahasa tersebut mendorong orang-orang yang menggunakan std :: move agar memiliki efisiensi yang lebih baik, jebakan seperti ini harus dicegah sedini mungkin, terutama untuk jenis kontradiksi literal yang jelas ini.

Saya setuju bahwa orang harus sadar bahwa memindahkan konstanta tidak mungkin, tetapi kewajiban ini tidak berarti bahwa penyusun dapat diam ketika kontradiksi yang jelas terjadi.

2. Objek tidak memiliki konstruktor bergerak

Secara pribadi, menurut saya ini adalah cerita yang terpisah dari keprihatinan OP, seperti yang dikatakan Chris Drew

@hvd Itu sepertinya sedikit bukan argumen bagi saya. Hanya karena saran OP tidak memperbaiki semua bug di dunia tidak selalu berarti itu adalah ide yang buruk (mungkin memang begitu, tapi bukan karena alasan yang Anda berikan). - Chris Drew

Bogeegee
sumber
1

Saya terkejut tidak ada yang menyebutkan aspek kompatibilitas ke belakang ini. Saya percaya, std::movesengaja dirancang untuk melakukan ini di C ++ 11. Bayangkan Anda bekerja dengan basis kode lama, yang sangat bergantung pada pustaka C ++ 98, jadi tanpa fallback pada tugas penyalinan, pemindahan akan merusak banyak hal.

mlo
sumber