Apa perbedaan antara std :: move dan std :: forward

160

Saya melihat ini di sini: Pindahkan Konstruktor memanggil basis-kelas Pindahkan Konstruktor

Bisakah seseorang menjelaskan:

  1. perbedaan antara std::movedan std::forward, lebih disukai dengan beberapa contoh kode?
  2. Bagaimana memikirkannya dengan mudah, dan kapan menggunakannya
aCuria
sumber
"Bagaimana cara memikirkannya dengan mudah, dan kapan menggunakannya" Anda menggunakan movesaat Anda ingin memindahkan suatu nilai, dan forwardkapan Anda ingin menggunakan penerusan yang sempurna. Ini bukan ilmu roket di sini;)
Nicol Bolas
move () melakukan cast tanpa syarat dimana forward () melakukan cast berdasarkan parameter yang telah dilewati.
RaGa__M

Jawaban:

159

std::movemengambil objek dan memungkinkan Anda untuk memperlakukannya sebagai sementara (suatu nilai). Meskipun ini bukan persyaratan semantik, biasanya fungsi yang menerima referensi ke nilai akan membatalkannya. Ketika Anda melihat std::move, itu menunjukkan bahwa nilai objek tidak boleh digunakan setelahnya, tetapi Anda masih dapat menetapkan nilai baru dan terus menggunakannya.

std::forwardmemiliki satu kasus penggunaan: untuk melemparkan parameter fungsi templated (di dalam fungsi) ke kategori nilai (nilai atau nilai) pemanggil yang digunakan untuk meneruskannya. Ini memungkinkan argumen nilai diturunkan sebagai nilai, dan nilai dinilai sebagai nilai, sebuah skema yang disebut "penerusan yang sempurna."

Untuk menggambarkan :

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }

template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
   (for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
    std::cout << "via std::forward: ";
    overloaded( std::forward< t >( arg ) );
    std::cout << "via std::move: ";
    overloaded( std::move( arg ) ); // conceptually this would invalidate arg
    std::cout << "by simple passing: ";
    overloaded( arg );
}

int main() {
    std::cout << "initial caller passes rvalue:\n";
    forwarding( 5 );
    std::cout << "initial caller passes lvalue:\n";
    int x = 5;
    forwarding( x );
}

Seperti yang disebutkan Howard, ada juga kesamaan karena kedua fungsi ini hanya menggunakan tipe referensi. Tetapi di luar kasus penggunaan khusus ini (yang mencakup 99,9% dari kegunaan cast referensi nilai), Anda harus menggunakan static_castsecara langsung dan menulis penjelasan yang baik tentang apa yang Anda lakukan.

Potatoswatter
sumber
Saya tidak yakin itu akurat yang std::forward's hanya kasus yang digunakan adalah forwarding sempurna dari argumen fungsi. Saya telah mengalami situasi di mana saya ingin meneruskan hal-hal lain dengan sempurna, seperti anggota objek.
Geoff Romer
@GeoffRomer Anggota parameter? Apakah kamu punya contoh?
Potatoswatter
Saya memposting contoh sebagai pertanyaan terpisah: stackoverflow.com/questions/20616958/…
Geoff Romer
Hmm, jadi jika saya mengerti ini dengan benar, itu berarti bahwa saya dapat menulis satu fungsi maju () di mana maju (5) beroperasi pada 5 seolah-olah saya meneruskannya dengan nilai di mana maju (x) beroperasi pada x seolah-olah saya melewatinya dengan referensi tanpa harus secara eksplisit menulis kelebihan.
iheanyi
@iheanyi Yap, itu idenya! Tapi itu harus menjadi templat, bukan hanya fungsi biasa.
Potatoswatter
63

Keduanya std::forwarddan std::movetidak lain adalah gips.

X x;
std::move(x);

Di atas melemparkan ekspresi lvalue xtipe X ke ekspresi rvalue tipe X (nilai x lebih tepatnya). movejuga dapat menerima nilai:

std::move(make_X());

dan dalam hal ini ini adalah fungsi identitas: mengambil nilai tipe X dan mengembalikan nilai tipe X.

Dengan std::forwardAnda dapat memilih tujuan sampai batas tertentu:

X x;
std::forward<Y>(x);

Melemparkan ekspresi nilai xdari tipe X ke ekspresi tipe Y. Ada kendala pada apa yang bisa Y.

Y dapat menjadi Pangkalan X yang dapat diakses, atau referensi ke Pangkalan X. Y dapat menjadi X, atau referensi ke X. Seseorang tidak dapat membuang kualifikasi cv forward, tetapi seseorang dapat menambahkan kualifikasi cv. Y tidak dapat menjadi tipe yang hanya dapat dikonversi dari X, kecuali melalui konversi Basis yang dapat diakses.

Jika Y adalah referensi lvalue, hasilnya akan menjadi ekspresi lvalue. Jika Y bukan referensi lvalue, hasilnya akan menjadi ekspresi rvalue (xvalue tepatnya).

forwarddapat mengambil argumen nilai hanya jika Y bukan referensi nilai tinggi. Artinya, Anda tidak dapat memberikan nilai ke nilai. Ini untuk alasan keamanan karena melakukan hal itu biasanya mengarah pada referensi yang menggantung. Tapi casting nilai untuk nilai adalah baik dan diizinkan.

Jika Anda mencoba menentukan Y untuk sesuatu yang tidak diizinkan, kesalahan akan ditangkap pada waktu kompilasi, bukan waktu berjalan.

Howard Hinnant
sumber
Jika saya meneruskan objek dengan sempurna ke suatu fungsi menggunakan std::forward, maka setelah fungsi itu dijalankan, dapatkah saya menggunakan objek itu? Saya menyadari bahwa, dalam kasus std::move, itu adalah perilaku yang tidak terdefinisi.
iammilind
1
Mengenai move: stackoverflow.com/a/7028318/576911 Untuk forward, jika Anda memberikan nilai lvn, API Anda akan bereaksi seolah menerima nilai lvn. Biasanya ini berarti nilainya akan tidak dimodifikasi. Tetapi jika ini bukan nilai konstanta, API Anda mungkin telah memodifikasinya. Jika Anda memberikan nilai, ini biasanya berarti bahwa API Anda mungkin telah pindah dari itu, dan dengan demikian stackoverflow.com/a/7028318/576911 akan berlaku.
Howard Hinnant
21

std::forwarddigunakan untuk meneruskan parameter persis seperti itu diteruskan ke suatu fungsi. Seperti yang ditunjukkan di sini:

Kapan harus menggunakan argumen std :: forward to forward?

Menggunakan std::movemenawarkan objek sebagai nilai, untuk mungkin cocok dengan konstruktor bergerak atau fungsi yang menerima nilai. Itu melakukan itu std::move(x)bahkan jika xbukan nilai sendiri.

Bo Persson
sumber