Jawaban:
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.
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.
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: P
Anda juga dapat menggunakan std::bind
untuk menangkap unique_ptr
:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
unique_ptr
referensi nilai tidak dapat mengikat ke int *
.
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:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = std::bind([](std::unique_ptr<int>& myPointerArg){
*myPointerArg = 4;
myPointerArg.reset(new int{237});
}, std::move(myPointer));
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:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
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 menyimpan std::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 menggunakan std::packaged_task
; itu akan melakukan pekerjaan yang sama dengan std::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.
#include <functional> // for std::bind
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
#include <future> // for std::packaged_task
#include <iostream> // printing
#include <type_traits> // for std::result_of
#include <cstddef>
void showPtr(const char* name, const std::unique_ptr<size_t>& ptr)
{
std::cout << "- &" << name << " = " << &ptr << ", " << name << ".get() = "
<< ptr.get();
if (ptr)
std::cout << ", *" << name << " = " << *ptr;
std::cout << std::endl;
}
// If you must use std::function, but your function is MoveConstructable
// but not CopyConstructable, you can wrap it in a shared pointer.
template <typename F>
class shared_function : public std::shared_ptr<F> {
public:
using std::shared_ptr<F>::shared_ptr;
template <typename ...Args>
auto operator()(Args&&...args) const
-> typename std::result_of<F(Args...)>::type
{
return (*(this->get()))(std::forward<Args>(args)...);
}
};
template <typename F>
shared_function<F> make_shared_fn(F&& f)
{
return shared_function<F>{
new typename std::remove_reference<F>::type{std::forward<F>(f)}};
}
int main()
{
std::unique_ptr<size_t> myPointer(new size_t{42});
showPtr("myPointer", myPointer);
std::cout << "Creating lambda\n";
#if __cplusplus == 201103L // C++ 11
// Use std::bind
auto lambda = std::bind([](std::unique_ptr<size_t>& myPointerArg){
showPtr("myPointerArg", myPointerArg);
*myPointerArg *= 56; // Reads our movable thing
showPtr("myPointerArg", myPointerArg);
myPointerArg.reset(new size_t{*myPointerArg * 237}); // Writes it
showPtr("myPointerArg", myPointerArg);
}, std::move(myPointer));
#elif __cplusplus > 201103L // C++14
// Use generalized capture
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
showPtr("myPointerCapture", myPointerCapture);
*myPointerCapture *= 56;
showPtr("myPointerCapture", myPointerCapture);
myPointerCapture.reset(new size_t{*myPointerCapture * 237});
showPtr("myPointerCapture", myPointerCapture);
};
#else
#error We need C++11
#endif
showPtr("myPointer", myPointer);
std::cout << "#1: lambda()\n";
lambda();
std::cout << "#2: lambda()\n";
lambda();
std::cout << "#3: lambda()\n";
lambda();
#if ONLY_NEED_TO_CALL_ONCE
// In some situations, std::packaged_task is an alternative to
// std::function, e.g., if you only plan to call it once. Otherwise
// you need to write your own wrapper to handle move-only function.
std::cout << "Moving to std::packaged_task\n";
std::packaged_task<void()> f{std::move(lambda)};
std::cout << "#4: f()\n";
f();
#else
// Otherwise, we need to turn our move-only function into one that can
// be copied freely. There is no guarantee that it'll only be copied
// once, so we resort to using a shared pointer.
std::cout << "Moving to std::function\n";
std::function<void()> f{make_shared_fn(std::move(lambda))};
std::cout << "#4: f()\n";
f();
std::cout << "#5: f()\n";
f();
std::cout << "#6: f()\n";
f();
#endif
}
Saya telah meletakkan program di atas pada Coliru , sehingga Anda dapat menjalankan dan bermain dengan kode.
Inilah beberapa keluaran khas ...
- &myPointer = 0xbfffe5c0, myPointer.get() = 0x7ae3cfd0, *myPointer = 42
Creating lambda
- &myPointer = 0xbfffe5c0, myPointer.get() = 0x0
#1: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 42
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 2352
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424
#2: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 31215744
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032
#3: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 1978493952
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
Moving to std::function
#4: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
#5: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2967666688
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808
#6: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 2022178816
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2515009536
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 makan std::function
.
Jika kita beralih menggunakan std::packaged_task
, itu bagian terakhir menjadi
Moving to std::packaged_task
#4: f()
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
jadi 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!
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.
// myPointer could be a parameter or something
std::unique_ptr<int> myPointer(new int);
// convert/move the unique ptr into a shared ptr
std::shared_ptr<int> mySharedPointer( std::move(myPointer) );
std::function<void(void)> = [mySharedPointer](){
*mySharedPointer = 4;
};
// at end of scope the original mySharedPointer is destroyed,
// but the copy still lives in the lambda capture.
.
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.
// myPointer could be a parameter or something
std::unique_ptr<int> myPointer(new int);
//FIXME:c++11 upgrade to new move capture on c++>=14
// "move" the pointer into a raw pointer
int* myRawPointer = myPointer.release();
// capture the raw pointer as a copy.
std::function<void(void)> = [myRawPointer](){
std::unique_ptr<int> capturedPointer(myRawPointer);
*capturedPointer = 4;
};
// ensure that the pointer's value is not accessible anymore after capturing
myRawPointer = nullptr;
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.
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.
#include <iostream>
#include <memory>
#include <utility>
#include <type_traits>
#include <functional>
namespace detail
{
enum selection_enabler { enabled };
}
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), ::detail::selection_enabler> \
= ::detail::enabled
// This allows forwarding an object using the copy constructor
template <typename T>
struct move_with_copy_ctor
{
// forwarding constructor
template <typename T2
// Disable constructor for it's own type, since it would
// conflict with the copy constructor.
, ENABLE_IF(
!std::is_same<std::remove_reference_t<T2>, move_with_copy_ctor>::value
)
>
move_with_copy_ctor(T2&& object)
: wrapped_object(std::forward<T2>(object))
{
}
// move object to wrapped_object
move_with_copy_ctor(T&& object)
: wrapped_object(std::move(object))
{
}
// Copy constructor being used as move constructor.
move_with_copy_ctor(move_with_copy_ctor const& object)
{
std::swap(wrapped_object, const_cast<move_with_copy_ctor&>(object).wrapped_object);
}
// access to wrapped object
T& operator()() { return wrapped_object; }
private:
T wrapped_object;
};
template <typename T>
move_with_copy_ctor<T> make_movable(T&& object)
{
return{ std::forward<T>(object) };
}
auto fn1()
{
std::unique_ptr<int, std::function<void(int*)>> x(new int(1)
, [](int * x)
{
std::cout << "Destroying " << x << std::endl;
delete x;
});
return [y = make_movable(std::move(x))]() mutable {
std::cout << "value: " << *y() << std::endl;
return;
};
}
int main()
{
{
auto x = fn1();
x();
std::cout << "object still not deleted\n";
x();
}
std::cout << "object was deleted\n";
}
The move_with_copy_ctor
kelas dan itu fungsi pembantu make_movable()
akan bekerja dengan bergerak tapi tidak objek menyatakan bahwa pihak. Untuk mendapatkan akses ke objek yang dibungkus, gunakan operator()()
.
Output yang diharapkan:
nilai: 1 objek masih belum dihapus nilai: 1 Menghancurkan 000000DFDD172280 objek telah dihapus
Nah, alamat pointer mungkin berbeda. ;)