2012-04-04 32 views
17

Trong khi làm việc trên this question, tôi nhận thấy rằng việc thực hiện GCC (v4.7) của std::function di chuyển các đối số của nó khi chúng được thực hiện theo giá trị. Các mã sau đây cho thấy hành vi này:Chức năng `std ::` có được phép di chuyển đối số của nó không?

#include <functional> 
#include <iostream> 

struct CopyableMovable 
{ 
    CopyableMovable()      { std::cout << "default" << '\n'; } 
    CopyableMovable(CopyableMovable const &) { std::cout << "copy" << '\n'; } 
    CopyableMovable(CopyableMovable &&)  { std::cout << "move" << '\n'; } 
}; 

void foo(CopyableMovable cm) 
{ } 

int main() 
{ 
    typedef std::function<void(CopyableMovable)> byValue; 

    byValue fooByValue = foo; 

    CopyableMovable cm; 
    fooByValue(cm); 
} 
// outputs: default copy move move 

Chúng ta thấy ở đây là một bản sao của cm được thực hiện (trong đó có vẻ hợp lý kể từ khi tham số của s byValue được lấy theo giá trị), nhưng sau đó có hai di chuyển. Kể từ khi function hoạt động trên một bản sao của cm, thực tế là nó di chuyển đối số của nó có thể được xem như là một chi tiết thực hiện không quan trọng. Tuy nhiên, hành vi này gây ra một số sự cố when using function together with bind:

#include <functional> 
#include <iostream> 

struct MoveTracker 
{ 
    bool hasBeenMovedFrom; 

    MoveTracker() 
     : hasBeenMovedFrom(false) 
    {} 
    MoveTracker(MoveTracker const &) 
     : hasBeenMovedFrom(false) 
    {} 
    MoveTracker(MoveTracker && other) 
     : hasBeenMovedFrom(false) 
    { 
     if (other.hasBeenMovedFrom) 
     { 
      std::cout << "already moved!" << '\n'; 
     } 
     else 
     { 
      other.hasBeenMovedFrom = true; 
     } 
    } 
}; 

void foo(MoveTracker, MoveTracker) {} 

int main() 
{ 
    using namespace std::placeholders; 
    std::function<void(MoveTracker)> func = std::bind(foo, _1, _1); 
    MoveTracker obj; 
    func(obj); // prints "already moved!" 
} 

Hành vi này có được phép theo tiêu chuẩn không? std::function có được phép di chuyển các đối số của nó không? Và nếu vậy, có bình thường khi chúng ta có thể chuyển đổi trình bao bọc được trả về bởi bind thành một tham số by-value std::function, mặc dù điều này kích hoạt hành vi không mong muốn khi xử lý nhiều lần xuất hiện của trình giữ chỗ?

+0

Dường như với tôi rằng vấn đề là nhiều hơn với trình giữ chỗ hơn 'std :: function'. Cụ thể, thực tế là khi tạo một 'tie', di chuyển được sử dụng từ đối số ban đầu cho cả hai kết quả mong đợi. –

+0

Thật thú vị, trình biên dịch Visual C++ 11 in "di chuyển bản sao mặc định" trong ví dụ đầu tiên và không in "đã được di chuyển!" trong lần thứ hai. Tôi tự hỏi nếu động thái bổ sung này có thể đến từ hoạt động bên trong của std :: chức năng và/hoặc chuyển tiếp hoàn hảo. –

+0

@MatthieuM. Bạn có thể xây dựng? Tôi không quen thuộc với việc triển khai trình giữ chỗ. Nếu vấn đề xuất phát từ trình giữ chỗ, làm thế nào vấn đề không phát sinh khi sử dụng 'auto' để suy ra kiểu" bind-wrapper ", thay vì sử dụng' std :: function'? –

Trả lời

16

std::function được chỉ định để chuyển các đối số đã cung cấp cho hàm được bao bọc với std::forward. ví dụ. cho std::function<void(MoveTracker)>, các nhà điều hành chức năng gọi là tương đương với

void operator(CopyableMovable a) 
{ 
    f(std::forward<CopyableMovable>(a)); 
} 

Kể từ std::forward<T> tương đương với std::move khi T không phải là một loại tài liệu tham khảo, điều này chiếm một trong những động thái trong ví dụ đầu tiên của bạn. Có thể là thứ hai xuất phát từ việc phải đi qua các lớp vô hướng bên trong std::function.

này sau đó cũng giải thích cho vấn đề bạn đang gặp phải với việc sử dụng std::bind như chức năng bọc: std::bindcũng quy định để chuyển tiếp các thông số của nó, và trong trường hợp này nó đang được thông qua một tài liệu tham khảo rvalue kết quả từ std::forward gọi bên trong std::function. Toán tử gọi hàm của biểu thức bind của bạn do đó chuyển tiếp tham chiếu rvalue tới từng đối số. Thật không may, vì bạn đã sử dụng lại trình giữ chỗ, nó là tham chiếu rvalue cho cùng một đối tượng trong cả hai trường hợp, do đó, đối với các loại có thể di chuyển được xây dựng trước sẽ di chuyển giá trị và tham số thứ hai sẽ nhận được một trình bao rỗng.

+1

Ồ, tôi chưa bao giờ nhận ra rằng 'std :: forward ' tương đương với 'std :: move '! Điều đó giải thích nhiều điều. –

Các vấn đề liên quan