Câu trả lời:
Trong C ++ 14, chúng ta sẽ có cái gọi là lambda tổng quát . Điều này cho phép chụp di chuyển. Sau đây sẽ là mã hợp pháp trong 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 ); } );
Nhưng nói chung chung hơn nhiều theo nghĩa là các biến bị bắt có thể được khởi tạo với bất cứ thứ gì tương tự như vậy:
auto lambda = [value = 0] mutable { return ++value; };
Trong C ++ 11, điều này là không thể, nhưng với một số thủ thuật liên quan đến các loại trình trợ giúp. May mắn thay, trình biên dịch Clang 3.4 đã thực hiện tính năng tuyệt vời này. Trình biên dịch sẽ được phát hành vào tháng 12 năm 2013 hoặc tháng 1 năm 2014, nếu tốc độ phát hành gần đây sẽ được giữ nguyên.
UPDATE: Các trình biên dịch Clang 3.4 được phát hành vào ngày 06 tháng một năm 2014 với tính năng cho biết.
Đây là một triển khai của một chức năng trợ make_rref
giúp giúp chụp di chuyển nhân tạo
#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) };
}
Và đây là trường hợp thử nghiệm cho chức năng đó đã chạy thành công trên gcc 4.7.3 của tôi.
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() );
}
Hạn chế ở đây là có thể lambda
sao chép và khi sao chép xác nhận trong hàm tạo sao chép rref_impl
không thành công dẫn đến lỗi thời gian chạy. Sau đây có thể là một giải pháp tốt hơn và thậm chí chung hơn vì trình biên dịch sẽ bắt lỗi.
Đây là một ý tưởng nữa, về cách thực hiện chụp lambda tổng quát. Việc sử dụng hàm capture()
(có triển khai được tìm thấy thêm) như sau:
#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() );
}
Đây lambda
là một đối tượng functor (gần như một lambda thật) đã bị bắt std::move(p)
khi nó được truyền tới capture()
. Đối số thứ hai capture
là lambda, lấy biến bị bắt làm đối số. Khi lambda
được sử dụng làm đối tượng hàm, thì tất cả các đối số được truyền cho nó sẽ được chuyển tiếp đến lambda bên trong dưới dạng đối số sau biến đã bắt. (Trong trường hợp của chúng tôi, không có thêm đối số nào được chuyển tiếp). Về cơ bản, giống như trong các giải pháp trước xảy ra. Đây là cách capture
thực hiện:
#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) );
}
Giải pháp thứ hai này cũng sạch hơn, vì nó vô hiệu hóa sao chép lambda, nếu loại bị bắt không thể sao chép. Trong giải pháp đầu tiên chỉ có thể được kiểm tra trong thời gian chạy với một assert()
.
moveCapture
trình bao bọc để truyền chúng dưới dạng đối số (phương thức này được sử dụng ở trên và trong Capn'Proto, thư viện của người tạo protobuff) hoặc chấp nhận rằng bạn yêu cầu trình biên dịch hỗ trợ nó: P
Bạn cũng có thể sử dụng std::bind
để chụp unique_ptr
:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
unique_ptr
tham chiếu giá trị không thể liên kết với một int *
.
myPointer
trong trường hợp này). Do đó, đoạn mã trên không biên dịch trong VS2013. Mặc dù vậy, nó vẫn ổn trong GCC 4.8.
Bạn có thể đạt được hầu hết những gì bạn muốn sử dụng std::bind
, như thế này:
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));
Mẹo ở đây là thay vì chụp đối tượng chỉ di chuyển của bạn trong danh sách chụp, chúng tôi biến nó thành đối số và sau đó sử dụng một phần ứng dụng thông qua std::bind
để làm cho nó biến mất. Lưu ý rằng lambda lấy nó bằng cách tham chiếu , vì nó thực sự được lưu trữ trong đối tượng liên kết. Tôi cũng đã thêm mã ghi vào đối tượng có thể di chuyển thực tế, vì đó là điều bạn có thể muốn làm.
Trong C ++ 14, bạn có thể sử dụng chụp lambda tổng quát để đạt được cùng một kết thúc, với mã này:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
Nhưng mã này không mua cho bạn bất cứ thứ gì bạn không có trong C ++ 11 thông qua std::bind
. (Có một số tình huống trong đó chụp lambda tổng quát mạnh hơn, nhưng không phải trong trường hợp này.)
Bây giờ chỉ có một vấn đề; bạn muốn đặt chức năng này trong một std::function
, nhưng lớp đó yêu cầu chức năng đó là CopyConstructible , nhưng không phải, đó chỉ là MoveConstructible vì nó lưu trữ một std::unique_ptr
thứ không phải là CopyConstructible .
Bạn phải giải quyết vấn đề với lớp bao bọc và một mức độ gián tiếp khác, nhưng có lẽ bạn không cần std::function
gì cả. Tùy thuộc vào nhu cầu của bạn, bạn có thể sử dụng std::packaged_task
; nó sẽ làm công việc tương tự như std::function
, nhưng nó không yêu cầu chức năng có thể sao chép được, chỉ có thể di chuyển (tương tự, std::packaged_task
chỉ có thể di chuyển). Nhược điểm là vì nó dự định sẽ được sử dụng cùng với std :: tương lai, nên bạn chỉ có thể gọi nó một lần.
Đây là một chương trình ngắn cho thấy tất cả các khái niệm này.
#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
}
Tôi đã đặt một chương trình trên cho Coliru , để bạn có thể chạy và chơi với mã.
Đây là một số đầu ra điển hình ...
- &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
Bạn có thể thấy các vị trí heap đang được sử dụng lại, cho thấy rằng các vị trí std::unique_ptr
hoạt động đúng. Bạn cũng thấy chức năng tự di chuyển xung quanh khi chúng ta bỏ nó trong một trình bao bọc mà chúng ta cung cấp std::function
.
Nếu chúng ta chuyển sang sử dụng std::packaged_task
, phần cuối cùng sẽ trở thành
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
vì vậy chúng ta thấy rằng hàm đã được di chuyển, nhưng thay vì được di chuyển lên heap, nó nằm trong std::packaged_task
ngăn xếp đó.
Hi vọng điêu nay co ich!
Muộn, nhưng như một số người (bao gồm cả tôi) vẫn bị mắc kẹt trên c ++ 11:
Thành thật mà nói, tôi không thực sự thích bất kỳ giải pháp được đăng. Tôi chắc chắn rằng chúng sẽ hoạt động, nhưng chúng đòi hỏi rất nhiều công cụ bổ sung và / hoặc std::bind
cú pháp mật mã ... và tôi không nghĩ rằng nó đáng để nỗ lực cho một giải pháp tạm thời như vậy dù sao cũng sẽ được tái cấu trúc khi nâng cấp lên c ++> = 14. Vì vậy, tôi nghĩ rằng giải pháp tốt nhất là tránh di chuyển bắt cho c ++ 11 hoàn toàn.
Thông thường, giải pháp đơn giản và dễ đọc nhất là sử dụng std::shared_ptr
, có thể sao chép và do đó việc di chuyển là hoàn toàn có thể tránh được. Nhược điểm là, nó kém hiệu quả hơn một chút, nhưng trong nhiều trường hợp, hiệu quả không quá quan trọng.
// 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.
.
Nếu trường hợp rất hiếm xảy ra, điều đó thực sự bắt buộc đối move
với con trỏ (ví dụ: bạn muốn xóa rõ ràng một con trỏ trong một luồng riêng biệt do thời gian xóa dài hoặc hiệu suất là rất quan trọng), đó là trường hợp duy nhất tôi vẫn sử dụng con trỏ thô trong c ++ 11. Đây là tất nhiên cũng có thể sao chép.
Thông thường tôi đánh dấu các trường hợp hiếm gặp này bằng một //FIXME:
để đảm bảo rằng nó được tái cấu trúc một khi nâng cấp lên 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;
Vâng, con trỏ thô khá nhíu mày trong những ngày này (và không phải không có lý do), nhưng tôi thực sự nghĩ rằng trong những trường hợp hiếm hoi (và tạm thời!) Này, chúng là giải pháp tốt nhất.
Tôi đã xem xét những câu trả lời này, nhưng tôi thấy ràng buộc khó đọc và hiểu. Vì vậy, những gì tôi đã làm là tạo ra một lớp di chuyển trên bản sao thay thế. Theo cách này, nó rõ ràng với những gì nó đang làm.
#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";
}
Các move_with_copy_ctor
lớp và chức năng helper của nó make_movable()
sẽ làm việc với bất kỳ đối tượng copyable di chuyển nhưng không phải. Để có quyền truy cập vào đối tượng được bọc, sử dụng operator()()
.
Sản lượng dự kiến:
giá trị: 1 đối tượng vẫn không bị xóa giá trị: 1 Phá hủy 000000DFDD172280 đối tượng đã bị xóa
Vâng, địa chỉ con trỏ có thể thay đổi. ;)