Tangkapan lambda yang digeneralisasi dalam C ++ 14
Dalam C ++ 14 kita akan memiliki apa yang disebut penangkapan lambda umum . Hal ini memungkinkan penangkapan langkah. Berikut ini adalah kode hukum dalam C ++ 14:
using namespace std;
// a unique_ptr is move-only
auto u = make_unique<some_type>( some, parameters );
// move the unique_ptr into the lambda
go.run( [ u{move(u)} ] { do_something_with( u ); } );
Tetapi jauh lebih umum dalam arti bahwa variabel yang ditangkap dapat diinisialisasi dengan hal-hal seperti:
auto lambda = [value = 0] mutable { return ++value; };
Di C ++ 11 ini belum memungkinkan, tetapi dengan beberapa trik yang melibatkan tipe pembantu. Untungnya, kompiler Dentang 3.4 sudah mengimplementasikan fitur luar biasa ini. Compiler akan dirilis Desember 2013 atau Januari 2014, jika kecepatan rilis terbaru akan dipertahankan.
UPDATE: The dentang 3.4 compiler dirilis pada 6 2014 Jan dengan kata fitur.
Solusi untuk menangkap langkah
Berikut ini adalah implementasi dari fungsi helper make_rref
yang membantu menangkap gerakan buatan
#include <cassert>
#include <memory>
#include <utility>
template <typename T>
struct rref_impl
{
rref_impl() = delete;
rref_impl( T && x ) : x{std::move(x)} {}
rref_impl( rref_impl & other )
: x{std::move(other.x)}, isCopied{true}
{
assert( other.isCopied == false );
}
rref_impl( rref_impl && other )
: x{std::move(other.x)}, isCopied{std::move(other.isCopied)}
{
}
rref_impl & operator=( rref_impl other ) = delete;
T && move()
{
return std::move(x);
}
private:
T x;
bool isCopied = false;
};
template<typename T> rref_impl<T> make_rref( T && x )
{
return rref_impl<T>{ std::move(x) };
}
Dan ini adalah test case untuk fungsi yang berjalan dengan sukses pada gcc 4.7.3 saya.
int main()
{
std::unique_ptr<int> p{new int(0)};
auto rref = make_rref( std::move(p) );
auto lambda =
[rref]() mutable -> std::unique_ptr<int> { return rref.move(); };
assert( lambda() );
assert( !lambda() );
}
Kelemahan di sini adalah yang lambda
dapat disalin dan ketika menyalin pernyataan dalam copy constructor darirref_impl
gagal menyebabkan bug runtime. Berikut ini mungkin solusi yang lebih baik dan lebih umum karena kompiler akan menangkap kesalahan.
Meniru penangkapan lambda umum di C ++ 11
Berikut ini satu ide lagi, tentang bagaimana menerapkan penangkapan lambda umum. Penggunaan fungsi capture()
(yang implementasinya ditemukan lebih jauh ke bawah) adalah sebagai berikut:
#include <cassert>
#include <memory>
int main()
{
std::unique_ptr<int> p{new int(0)};
auto lambda = capture( std::move(p),
[]( std::unique_ptr<int> & p ) { return std::move(p); } );
assert( lambda() );
assert( !lambda() );
}
Berikut lambda
adalah objek functor (hampir lambda nyata) yang telah ditangkap std::move(p)
saat diteruskan ke capture()
. Argumen kedua capture
adalah lambda yang mengambil variabel yang ditangkap sebagai argumen. Ketika lambda
digunakan sebagai objek fungsi, maka semua argumen yang diteruskan ke itu akan diteruskan ke lambda internal sebagai argumen setelah variabel yang ditangkap. (Dalam kasus kami tidak ada argumen lebih lanjut untuk diteruskan). Intinya, sama seperti pada solusi sebelumnya yang terjadi. Begini caranya capture
diimplementasikan:
#include <utility>
template <typename T, typename F>
class capture_impl
{
T x;
F f;
public:
capture_impl( T && x, F && f )
: x{std::forward<T>(x)}, f{std::forward<F>(f)}
{}
template <typename ...Ts> auto operator()( Ts&&...args )
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
template <typename ...Ts> auto operator()( Ts&&...args ) const
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
};
template <typename T, typename F>
capture_impl<T,F> capture( T && x, F && f )
{
return capture_impl<T,F>(
std::forward<T>(x), std::forward<F>(f) );
}
Solusi kedua ini juga lebih bersih, karena menonaktifkan menyalin lambda, jika jenis yang ditangkap tidak dapat disalin. Dalam solusi pertama yang hanya dapat diperiksa saat runtime dengan assert()
.
moveCapture
pembungkus untuk meneruskannya sebagai argumen (metode ini digunakan di atas dan di Capn'Proto, perpustakaan oleh pencipta protobuffs) atau buat saja terima bahwa Anda memerlukan kompiler yang mendukungnya: PAnda juga dapat menggunakan
std::bind
untuk menangkapunique_ptr
:sumber
unique_ptr
referensi nilai tidak dapat mengikat keint *
.myPointer
dalam hal ini). Karenanya kode di atas tidak dikompilasi dalam VS2013. Ini bekerja dengan baik di GCC 4.8.Anda dapat mencapai sebagian besar dari apa yang ingin Anda gunakan
std::bind
, seperti ini:Kuncinya di sini adalah bahwa alih-alih menangkap objek hanya bergerak Anda dalam daftar tangkapan, kami menjadikannya argumen dan kemudian menggunakan aplikasi parsial via
std::bind
untuk membuatnya menghilang. Perhatikan bahwa lambda mengambilnya dengan referensi , karena sebenarnya disimpan dalam objek bind. Saya juga menambahkan kode yang menulis ke objek bergerak yang sebenarnya, karena itu sesuatu yang mungkin ingin Anda lakukan.Di C ++ 14, Anda dapat menggunakan tangkapan lambda umum untuk mencapai tujuan yang sama, dengan kode ini:
Tetapi kode ini tidak membelikan Anda apa pun yang tidak Anda miliki di C ++ 11 via
std::bind
. (Ada beberapa situasi di mana penangkapan lambda secara umum lebih kuat, tetapi tidak dalam hal ini.)Sekarang hanya ada satu masalah; Anda ingin meletakkan fungsi ini dalam a
std::function
, tetapi kelas itu mengharuskan fungsi tersebut menjadi CopyConstructible , tetapi tidak, itu hanya MoveConstructible karena menyimpanstd::unique_ptr
yang bukan CopyConstructible .Anda dapat mengatasi masalah dengan kelas pembungkus dan tingkat tipuan lainnya, tetapi mungkin Anda tidak perlu
std::function
sama sekali. Tergantung pada kebutuhan Anda, Anda mungkin dapat menggunakanstd::packaged_task
; itu akan melakukan pekerjaan yang sama denganstd::function
, tetapi tidak memerlukan fungsi untuk dapat disalin, hanya bergerak (sama,std::packaged_task
hanya bergerak). Kelemahannya adalah karena ini dimaksudkan untuk digunakan bersama dengan std :: future, Anda hanya dapat menyebutnya sekali.Berikut ini adalah program singkat yang menunjukkan semua konsep ini.
Saya telah meletakkan program di atas pada Coliru , sehingga Anda dapat menjalankan dan bermain dengan kode.
Inilah beberapa keluaran khas ...
Anda bisa melihat tumpukan lokasi yang digunakan kembali, menunjukkan bahwa
std::unique_ptr
itu berfungsi dengan baik. Anda juga melihat fungsi itu sendiri bergerak ketika kita menyimpannya di pembungkus yang kita beri makanstd::function
.Jika kita beralih menggunakan
std::packaged_task
, itu bagian terakhir menjadijadi kita melihat bahwa fungsi telah dipindahkan, tetapi alih-alih dipindahkan ke tumpukan, itu ada di dalam
std::packaged_task
yang ada di tumpukan.Semoga ini membantu!
sumber
Terlambat, tetapi karena beberapa orang (termasuk saya) masih terjebak pada c ++ 11:
Sejujurnya, saya tidak terlalu suka solusi yang diposting. Saya yakin mereka akan bekerja, tetapi mereka membutuhkan banyak hal tambahan dan / atau
std::bind
sintaksis samar ... dan saya tidak berpikir itu sepadan dengan usaha untuk solusi sementara yang akan dire-refoured lagi ketika memperbarui ke c ++> = 14. Jadi saya pikir solusi terbaik adalah menghindari penangkapan untuk c ++ 11 sepenuhnya.Biasanya solusi yang paling sederhana dan paling mudah dibaca adalah menggunakan
std::shared_ptr
, yang dapat disalin dan langkah ini benar-benar dapat dihindari. Kelemahannya, itu sedikit kurang efisien, tetapi dalam banyak kasus efisiensi tidak begitu penting..
Jika kasus yang sangat jarang terjadi, itu benar-benar wajib untuk
move
pointer (misalnya Anda ingin secara eksplisit menghapus pointer di utas terpisah karena durasi hapus yang lama, atau kinerja sangat penting), itu adalah satu-satunya kasus di mana saya masih menggunakan pointer mentah dalam c ++ 11. Ini tentu saja juga dapat disalin.Biasanya saya menandai kasus-kasus langka ini dengan
//FIXME:
untuk memastikan bahwa itu di-refactored setelah ditingkatkan ke c ++ 14.Ya, pointer mentah sangat disukai pada hari-hari ini (dan bukan tanpa alasan), tapi saya benar-benar berpikir dalam kasus yang jarang (dan sementara!) Ini adalah solusi terbaik.
sumber
Saya melihat jawaban-jawaban ini, tetapi saya merasa sulit untuk membaca dan memahami. Jadi yang saya lakukan adalah membuat kelas yang pindah pada salinan sebagai gantinya. Dengan cara ini, itu eksplisit dengan apa yang dilakukannya.
The
move_with_copy_ctor
kelas dan itu fungsi pembantumake_movable()
akan bekerja dengan bergerak tapi tidak objek menyatakan bahwa pihak. Untuk mendapatkan akses ke objek yang dibungkus, gunakanoperator()()
.Output yang diharapkan:
Nah, alamat pointer mungkin berbeda. ;)
Demo
sumber
Ini sepertinya bekerja pada gcc4.8
sumber