Yanıtlar:
C ++ 14'te genelleştirilmiş lambda yakalama olarak adlandırılacak . Bu hareket yakalamayı mümkün kılar. Aşağıdaki C ++ 14 yasal kod olacaktır:
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 ); } );
Ancak yakalanan değişkenlerin böyle bir şeyle başlatılabilmesi anlamında çok daha geneldir:
auto lambda = [value = 0] mutable { return ++value; };
C ++ 11'de bu henüz mümkün değildir, ancak yardımcı türleri içeren bazı hilelerle. Neyse ki, Clang 3.4 derleyicisi bu harika özelliği zaten uygulamaktadır. Derleyici, son sürüm hızını koruyacaksa Aralık 2013 veya Ocak 2014'te piyasaya sürülecektir .
GÜNCELLEME: Clang 3.4 derleyici söz konusu özelliği ile 6 Ocak 2014 tarihinde serbest bırakıldı.
make_rref
Yapay hareket yakalamaya yardımcı olan bir yardımcı fonksiyonun uygulaması
#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) };
}
Ve işte bu fonksiyon için benim gcc 4.7.3 başarıyla çalıştırılan bir test durumda.
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() );
}
Buradaki dezavantaj lambda
, kopyalanabilir ve kopya yapıcısındaki iddianın rref_impl
bir çalışma zamanı hatasına yol açması başarısız olmasıdır. Aşağıdakiler daha iyi ve daha genel bir çözüm olabilir, çünkü derleyici hatayı yakalayacaktır.
Genelleştirilmiş lambda yakalamanın nasıl uygulanacağı hakkında bir fikir daha. capture()
(Uygulaması daha aşağıda bulunan) fonksiyonunun kullanımı aşağıdaki gibidir:
#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() );
}
İşte lambda
yakalamış bir funktor nesne (neredeyse gerçek lamda) 'dir std::move(p)
o geçirilir olarak capture()
. İkinci argümanı capture
, yakalanan değişkeni argüman olarak alan bir lambdadır. Bir lambda
işlev nesnesi olarak kullanıldığında, kendisine iletilen tüm bağımsız değişkenler, yakalanan değişkenin ardından bağımsız değişken olarak dahili lambda'ya iletilir. (Bizim durumumuzda iletilecek başka argüman yoktur). Esasen, önceki çözümdeki ile aynıdır. Nasıl capture
uygulandığı aşağıda açıklanmıştır :
#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) );
}
Bu ikinci çözüm de daha temizdir, çünkü yakalanan tip kopyalanamazsa lambda kopyalamayı devre dışı bırakır. Sadece bir ile çalışma zamanında kontrol edilebilir ilk çözümde assert()
.
moveCapture
bunları argüman olarak iletmek için bir sarıcı kullanın (bu yöntem yukarıda ve protobuffların yaratıcısı tarafından bir kitaplık olan Capn'Proto'da kullanılır) veya sadece onu destekleyen derleyicilere ihtiyacınız olduğunu kabul edin: P
Şunları std::bind
yakalamak için de kullanabilirsiniz unique_ptr
:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
unique_ptr
rvalue referansı bir int *
.
myPointer
dosyasını bu durumda) . Bu nedenle, yukarıdaki kod VS2013'te derlenmez. Gerçi GCC 4.8'de iyi çalışıyor.
Kullanmak istediğiniz şeylerin çoğuna şu şekilde ulaşabilirsiniz std::bind
:
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));
Buradaki hile, yakalama listesinde salt hareket eden nesnenizi yakalamak yerine, onu bir argüman haline getirmek ve daha sonra std::bind
ortadan kaldırmak için kısmi uygulamayı kullanmaktır . Lambda'nın referans olarak aldığını unutmayın , çünkü aslında bağlama nesnesinde saklanır. Ayrıca kod eklediği yazıyor , gerçek hareketli nesneye , çünkü bu yapmak isteyebileceğiniz bir şey.
C ++ 14'te, aynı kodları elde etmek için şu kodla genelleştirilmiş lambda yakalamayı kullanabilirsiniz:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
Ancak bu kod size C ++ 11 ile sahip olmadığınız hiçbir şeyi satın almaz std::bind
. (Genelleştirilmiş lambda yakalamanın daha güçlü olduğu, ancak bu durumda olmadığı bazı durumlar vardır.)
Şimdi sadece bir sorun var; bu işlevi a'ya koymak istediniz std::function
, ancak bu sınıf işlevin a'ya CopyConstructible , ancak değil, yalnızca MoveConstructible, çünkü CopyConstructiblestd::unique_ptr
olmayan bir depolama .
Sarıcı sınıfı ve başka bir dolaylama düzeyi ile ilgili sorunu çözmek için çalışabilirsiniz, ancak belki de hiç ihtiyacınız yoktur std::function
. İhtiyaçlarınıza bağlı olarak,std::packaged_task
; aynı işi yapar std::function
, ancak işlevin kopyalanabilmesini gerektirmez, sadece taşınabilir (benzer şekilde std::packaged_task
sadece taşınabilir). Dezavantajı, std :: future ile birlikte kullanılması amaçlandığından, yalnızca bir kez çağırabilirsiniz.
İşte tüm bu kavramları gösteren kısa bir program.
#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
}
Ben Coliru yukarıdaki bir program koydum , böylece çalıştırmak ve kod ile oynayabilirsiniz.
İşte bazı tipik çıktı ...
- &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
Öbek konumlarının yeniden kullanıldığını, std::unique_ptr
düzgün çalıştığını görebilirsiniz. Ayrıca, beslediğimiz bir sarıcıda sakladığımızda işlevin kendisinin hareket ettiğini görüyorsunuz std::function
.
Kullanmaya geçersek std::packaged_task
geçersek, son kısım
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
bu nedenle işlevin taşındığını görüyoruz, ancak yığına taşınmak yerine, yığının std::packaged_task
üzerinde yer alıyor.
Bu yardımcı olur umarım!
Geç, ama bazı insanlar (ben dahil) hala c ++ 11'e takılı kalıyor:
Dürüst olmak gerekirse, yayınlanan çözümlerden hiçbirini gerçekten sevmiyorum. Eminim işe yarayacaklar, ancak çok fazla ek şey ve / veya şifreli std::bind
sözdizimi gerektiriyorlar ... ve c ++ 'ya yükseltirken yine de yeniden düzenlenecek geçici bir çözüm için çabaya değeceğini düşünmüyorum> = 14. Bence en iyi çözüm c ++ 11 için hareket yakalamayı tamamen önlemek.
Genellikle en basit ve en iyi okunabilir çözüm, std::shared_ptr
kopyalanabilir olan ve böylece hareketin tamamen önlenebilir olduğu kullanmaktır. Dezavantajı, biraz daha az verimli olması, ancak çoğu durumda verimlilik o kadar önemli değildir.
// 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.
.
Çok nadir bir durum ortaya çıkarsa, move
işaretçinin (örneğin, uzun silme süresi nedeniyle bir işaretçiyi ayrı bir iş parçacığında açıkça silmek istiyorsanız veya performans kesinlikle çok önemlidir), hala kullandığım tek durum bu c ++ 11'de ham işaretçiler. Bunlar elbette kopyalanabilir.
Genellikle bir //FIXME:
kez c ++ 14'e yükseltildiğinde yeniden düzenlenmesi için bu nadir durumlarda bir işaretlerim .
// 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;
Evet, ham işaretçiler bu günlerde (ve sebepsiz değil) oldukça kaşlarını çattı, ancak bu nadir (ve geçici!) Durumlarda gerçekten en iyi çözüm olduğunu düşünüyorum.
Bu cevaplara bakıyordum, ama okumayı ve anlamayı zor buldum. Yaptığım şey bunun yerine kopyaya geçen bir sınıf yapmaktı. Bu şekilde, ne yaptığı açıktır.
#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";
}
move_with_copy_ctor
Sınıf ve 's yardımcı işlevi make_movable()
herhangi hareketli değil copyable nesne ile çalışacaktır. Sarılmış nesneye erişmek için operator()()
.
Beklenen çıktı:
değer: 1 nesne hala silinmedi değer: 1 000000DFDD172280 yok ediliyor nesne silindi
İşaretçi adresi değişebilir. ;)
Bu gcc4.8 üzerinde çalışıyor gibi görünüyor
#include <memory>
#include <iostream>
struct Foo {};
void bar(std::unique_ptr<Foo> p) {
std::cout << "bar\n";
}
int main() {
std::unique_ptr<Foo> p(new Foo);
auto f = [ptr = std::move(p)]() mutable {
bar(std::move(ptr));
};
f();
return 0;
}