ย้ายการจับในแลมบ์ดา


157

ฉันจะจับภาพด้วยการย้าย (หรือที่รู้จักกันว่าการอ้างอิง rvalue) ในแลมบ์ดา C ++ 11 ได้อย่างไร

ฉันกำลังพยายามเขียนสิ่งนี้:

std::unique_ptr<int> myPointer(new int);

std::function<void(void)> example = [std::move(myPointer)]{
   *myPointer = 4;
};

คำตอบ:


163

แลมบ์ดาทั่วไปจับใน C ++ 14

ใน C ++ 14 เราจะมีที่เรียกว่าทั่วไปจับแลมบ์ดา สิ่งนี้ช่วยให้การจับการย้าย ต่อไปนี้จะเป็นรหัสทางกฎหมายใน 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 ); } ); 

แต่โดยทั่วไปแล้วในแง่ที่ว่าตัวแปรที่บันทึกไว้สามารถเริ่มต้นด้วยอะไรก็ได้เช่น:

auto lambda = [value = 0] mutable { return ++value; };

ใน C ++ 11 ยังไม่สามารถทำได้ แต่มีเทคนิคบางอย่างที่เกี่ยวข้องกับประเภทผู้ช่วยเหลือ โชคดีที่คอมไพเลอร์ Clang 3.4 ใช้ฟีเจอร์ที่ยอดเยี่ยมนี้แล้ว คอมไพเลอร์จะได้รับการปล่อยตัวในเดือนธันวาคม 2013 หรือมกราคม 2014 หากก้าวที่วางจำหน่ายล่าสุดจะถูกเก็บไว้

UPDATE: เสียงดังกราว 3.4 คอมไพเลอร์ได้รับการปล่อยตัวเมื่อวันที่ 6 มกราคม 2014 ที่มีคุณสมบัติดังกล่าว

วิธีแก้ปัญหาสำหรับการจับการย้าย

นี่คือการใช้งานฟังก์ชั่นตัวmake_rrefช่วยซึ่งช่วยในการดักจับการย้ายแบบเทียม

#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) };
}

และนี่คือกรณีทดสอบสำหรับฟังก์ชั่นนั้นที่ประสบความสำเร็จใน gcc 4.7.3 ของฉัน

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() );
}

ข้อเสียคือที่นี่สามารถlambdaคัดลอกและเมื่อคัดลอกการยืนยันในตัวสร้างการคัดลอกของความrref_implล้มเหลวที่นำไปสู่ข้อผิดพลาด runtime ต่อไปนี้อาจเป็นวิธีแก้ปัญหาที่ดีกว่าและทั่วไปกว่าเนื่องจากคอมไพเลอร์จะตรวจจับข้อผิดพลาด

การจำลองการจับแลมบ์ดาทั่วไปใน C ++ 11

ต่อไปนี้เป็นแนวคิดเพิ่มเติมอีกประการหนึ่งเกี่ยวกับวิธีใช้แลมบ์ดาแคปทั่วไป การใช้ฟังก์ชั่นcapture()(ซึ่งพบว่าการใช้งานต่อไปลง) มีดังนี้:

#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() );
}

นี่lambdaเป็นวัตถุ functor (เกือบแลมบ์ดาจริง) ซึ่งได้ถูกจับในขณะที่มันถูกส่งไปยังstd::move(p) capture()อาร์กิวเมนต์ที่สองcaptureคือแลมบ์ดาซึ่งรับค่าตัวแปรที่จับได้เป็นอาร์กิวเมนต์ เมื่อlambdaมีการใช้เป็นวัตถุฟังก์ชั่นแล้วข้อโต้แย้งทั้งหมดที่ถูกส่งผ่านไปยังมันจะถูกส่งต่อไปยังแลมบ์ดาภายในเป็นข้อโต้แย้งหลังจากตัวแปรจับ (ในกรณีของเราไม่มีข้อโต้แย้งเพิ่มเติมที่จะถูกส่งต่อ) เป็นหลักเช่นเดียวกับในการแก้ปัญหาก่อนหน้านี้เกิดขึ้น นี่คือวิธีcaptureการใช้งาน:

#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) );
}

โซลูชันที่สองนี้ยังสะอาดกว่าเพราะปิดใช้งานการคัดลอกแลมบ์ดาถ้ารูปแบบการจับไม่สามารถคัดลอกได้ assert()ในการแก้ปัญหาแรกที่สามารถตรวจสอบได้เท่านั้นที่รันไทม์กับ


ฉันใช้งานมานานกับ G ++ - 4.8 -std = c ++ 11 และฉันคิดว่ามันเป็นคุณสมบัติ C ++ 11 ตอนนี้ฉันคุ้นเคยกับการใช้สิ่งนี้และรู้ทันทีว่ามันเป็นคุณสมบัติ C ++ 14 ... ฉันควรทำอย่างไร !!
RnMss

@RnMss คุณหมายถึงคุณลักษณะใด แลมบ์ดาทั่วไป
Ralph Tandetzky

@RalphTandetzky ฉันคิดว่าอย่างนั้นฉันเพิ่งตรวจสอบและรุ่น clang ที่มาพร้อมกับ XCode ดูเหมือนจะสนับสนุนด้วยเช่นกัน! มันเตือนว่าเป็นนามสกุล C ++ 1y แต่ใช้งานได้
Christopher Tarquini

@RnMss ใช้moveCapturewrapper เพื่อส่งเป็นอาร์กิวเมนต์ (วิธีนี้ใช้ด้านบนและใน Capn'Proto ห้องสมุดโดยผู้สร้าง protobuffs) หรือยอมรับว่าคุณต้องการคอมไพเลอร์ที่สนับสนุน: P
Christopher Tarquini

9
ไม่จริงมันไม่ใช่สิ่งเดียวกัน ตัวอย่าง: คุณต้องการวางไข่เธรดด้วยแลมบ์ดาซึ่งการย้ายจะจับตัวชี้ที่ไม่ซ้ำกัน ฟังก์ชั่นการวางไข่อาจจะกลับมาและ unique_ptr ออกไปนอกขอบเขตก่อนที่ functor จะถูกประหารชีวิต ดังนั้นคุณมีการอ้างอิงห้อยไปยัง unique_ptr ยินดีต้อนรับสู่ undefined-behavior-land
Ralph Tandetzky

76

คุณสามารถใช้std::bindเพื่อจับภาพunique_ptr:

std::function<void()> f = std::bind(
                              [] (std::unique_ptr<int>& p) { *p=4; },
                              std::move(myPointer)
                          );

2
ขอขอบคุณสำหรับการโพสต์นี้!
mmocny

4
คุณได้ตรวจสอบแล้วหากรหัสรวบรวม? มันไม่ได้ดูเพื่อให้ฉันตั้งแต่แรกชื่อตัวแปรจะหายไปและประการที่สองunique_ptrการอ้างอิง rvalue int *ไม่สามารถผูกกับ
Ralph Tandetzky

7
โปรดทราบว่าใน Visual Studio 2013 การแปลง std :: bind เป็นฟังก์ชัน std :: ยังคงส่งผลให้มันคัดลอกตัวแปรที่ถูกผูกไว้ทั้งหมด ( myPointerในกรณีนี้) ดังนั้นรหัสข้างต้นไม่ได้รวบรวมใน VS2013 มันทำงานได้ดีใน GCC 4.8
Alan

22

คุณสามารถบรรลุสิ่งที่คุณต้องการใช้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));

เคล็ดลับที่นี่คือแทนที่จะจับภาพวัตถุที่เคลื่อนไหวอย่างเดียวของคุณในรายการจับภาพเราทำให้มันเป็นอาร์กิวเมนต์แล้วใช้แอปพลิเคชันบางส่วนผ่านstd::bindเพื่อทำให้หายไป โปรดทราบว่าแลมบ์ดานำมาอ้างอิงโดยจริงเพราะมันถูกเก็บไว้ในวัตถุที่ผูกไว้ ฉันยังเพิ่มรหัสที่เขียนไปยังวัตถุที่เคลื่อนย้ายได้จริงเพราะนั่นเป็นสิ่งที่คุณอาจต้องการทำ

ใน C ++ 14 คุณสามารถใช้การจับแลมบ์ดาทั่วไปเพื่อให้ได้จุดสิ้นสุดเดียวกันโดยใช้รหัสนี้:

std::unique_ptr<int> myPointer(new int{42});

auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
    *myPointerCapture = 56;
    myPointerCapture.reset(new int{237});
};

แต่รหัสนี้ไม่ได้ซื้ออะไรคุณไม่ได้มีใน C ++ 11 std::bindผ่าน (มีบางสถานการณ์ที่การจับแลมบ์ดาทั่วไปมีประสิทธิภาพมากกว่า แต่ไม่ใช่ในกรณีนี้)

ขณะนี้มีเพียงปัญหาเดียวคือ คุณต้องการที่จะนำฟังก์ชั่นนี้ในstd::functionแต่ระดับที่ต้องมีฟังก์ชั่นที่จะCopyConstructibleแต่มันไม่ได้เป็นเพียงMoveConstructibleเพราะการจัดเก็บstd::unique_ptrที่ไม่ได้CopyConstructible

คุณต้องแก้ไขปัญหาด้วยคลาส wrapper และการอ้อมอีกระดับหนึ่ง แต่บางทีคุณอาจไม่ต้องการstd::functionเลย ทั้งนี้ขึ้นอยู่กับความต้องการของคุณคุณอาจจะสามารถใช้งานstd::packaged_task; มันจะทำงานเหมือนกันstd::functionแต่ไม่จำเป็นต้องใช้ฟังก์ชั่นที่สามารถคัดลอกได้std::packaged_taskเท่านั้นที่สามารถเคลื่อนย้ายได้ ข้อเสียคือเนื่องจากมีวัตถุประสงค์เพื่อใช้ร่วมกับ std :: future คุณจึงสามารถเรียกได้เพียงครั้งเดียวเท่านั้น

นี่คือโปรแกรมย่อที่แสดงแนวคิดเหล่านี้ทั้งหมด

#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
}

ฉันได้ใส่โปรแกรมข้างต้นลงบน Coliruเพื่อให้คุณสามารถเรียกใช้และเล่นกับรหัส

นี่คือผลลัพธ์ทั่วไป ...

- &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

คุณจะเห็นสถานที่ตั้งของกองที่ถูกนำมาใช้ซ้ำแสดงให้เห็นว่าสถานที่std::unique_ptrทำงานได้อย่างถูกต้อง นอกจากนี้คุณยังดูฟังก์ชั่นของตัวเองย้ายไปรอบ ๆ std::functionเมื่อเราซ่อนไว้ในเสื้อคลุมที่เรากินไป

หากเราเปลี่ยนไปใช้std::packaged_taskส่วนสุดท้ายจะกลายเป็น

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

ดังนั้นเราจึงเห็นว่าฟังก์ชั่นถูกย้ายไปแล้ว แต่แทนที่จะย้ายไปที่กองมันอยู่ภายในstd::packaged_taskที่อยู่ในกอง

หวังว่านี่จะช่วยได้!


4

ล่าช้า แต่เป็นบางคน (รวมถึงฉัน) ยังคงติดอยู่ใน c ++ 11:

พูดตามตรงฉันไม่ชอบโซลูชันที่โพสต์เลย ฉันแน่ใจว่าพวกเขาจะทำงานได้ แต่พวกเขาต้องการสิ่งเพิ่มเติมและ / หรือstd::bindไวยากรณ์ที่เข้ารหัสลับ... และฉันไม่คิดว่ามันคุ้มค่ากับความพยายามในการแก้ปัญหาชั่วคราวเช่นนี้ซึ่งจะได้รับการปรับสภาพใหม่เมื่ออัพเกรดเป็น c ++> = 14. ดังนั้นฉันคิดว่าทางออกที่ดีที่สุดคือหลีกเลี่ยงการย้ายการจับสำหรับ c ++ 11 โดยสมบูรณ์

โดยปกติแล้ววิธีแก้ปัญหาที่ง่ายและดีที่สุดที่อ่านได้คือการใช้std::shared_ptrซึ่งสามารถคัดลอกได้ดังนั้นการย้ายจึงหลีกเลี่ยงได้ ข้อเสียคือมันมีประสิทธิภาพน้อยกว่าเล็กน้อย แต่ในหลาย ๆ กรณีประสิทธิภาพไม่สำคัญเท่าไหร่

// 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.

.

หากเกิดกรณีที่หายากมากมันเป็นเรื่องจำเป็นอย่างยิ่งสำหรับmoveตัวชี้ (เช่นคุณต้องการลบตัวชี้อย่างชัดเจนในเธรดแยกต่างหากเนื่องจากระยะเวลาการลบยาวหรือประสิทธิภาพเป็นสิ่งสำคัญอย่างยิ่ง) นั่นเป็นกรณีเดียวที่ฉันยังคงใช้ พอยน์เตอร์ดิบใน c ++ 11 แน่นอนเหล่านี้สามารถคัดลอกได้

โดยปกติฉันจะทำเครื่องหมายกรณีที่หายากเหล่านี้ด้วย//FIXME:เพื่อให้แน่ใจว่าจะได้รับการสร้างใหม่เมื่ออัพเกรดเป็น 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;

ใช่พอยน์เตอร์ดิบค่อนข้างขมวดคิ้วในทุกวันนี้ (และไม่ไร้เหตุผล) แต่ฉันคิดว่าในกรณีที่หายาก (และชั่วคราว!) เหล่านี้เป็นทางออกที่ดีที่สุด


ขอบคุณการใช้ C ++ 14 และไม่ใช่โซลูชันอื่น ๆ นั้นดี บันทึกวันของฉัน!
Yoav Sternberg

1

ฉันกำลังดูคำตอบเหล่านี้ แต่ฉันพบว่าอ่านและเข้าใจได้ยาก ดังนั้นสิ่งที่ฉันทำคือสร้างคลาสที่ย้ายมาทำสำเนาแทน ด้วยวิธีนี้มันชัดเจนกับสิ่งที่กำลังทำ

#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ระดับและฟังก์ชั่นผู้ช่วยที่make_movable()จะทำงานร่วมกับวัตถุที่สามารถเคลื่อนย้าย แต่ไม่ copyable ใด ๆ operator()()ได้รับการเข้าถึงไปยังวัตถุที่ห่อใช้

ผลลัพธ์ที่คาดหวัง:

ค่า: 1
วัตถุยังไม่ถูกลบ
ค่า: 1
ทำลาย 000000DFDD172280
วัตถุถูกลบ

ที่อยู่ของตัวชี้อาจแตกต่างกัน ;)

Demo


1

ดูเหมือนว่าจะทำงานกับ gcc4.8

#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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.