2012-04-04 2 views
17

this question에서 작업하면서 GCC (v4.7)의 구현 인 std::function은 값으로 가져갈 때 인수가 이동한다는 것을 알게되었습니다. 다음 코드는이 동작을 보여줍니다`std :: function`이 인수를 이동할 수 있습니까?

#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 

우리는 여기에서 볼 cm의 사본이 수행되도록합니다 (byValue의 매개 변수 값에 의해 촬영되어 있기 때문에 합리적인 것으로 보인다),하지만이 움직임이있다. functioncm 사본에서 작동하고 있기 때문에 인수가 이동한다는 사실은 중요하지 않은 구현 세부 사항으로 볼 수 있습니다. 그러나이 동작은 몇 가지 문제 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!" 
} 

표준에 의해 허용이 문제인가? std::function은 (는) 해당 인수를 이동할 수 있습니까? 그렇다면 bind에 의해 반환 된 래퍼를 by-value 매개 변수가있는 std::function으로 변환 할 수 있습니다. 이는 여러 개의 자리 표시자를 처리 할 때 예기치 않은 동작을 유발할 수 있습니까?

+0

문제는 'std :: function'보다 placeholder가 더 많은 것으로 보입니다. 즉, '동점'을 만들 때 원본 인수에서 예상되는 두 출력 모두로 이동한다는 사실. –

+0

흥미롭게도 Visual C++ 11 컴파일러는 첫 번째 예제에서 "default copy move"를 인쇄하고 "already moved!" 두 번째. 이 추가 이동이 std :: function 및/또는 perfect forwarding의 내부 동작에서 오는 것인지 궁금합니다. –

+0

@MatthieuM. 당신은 정교 할 수 있습니까? 자리 표시 자 구현에 익숙하지 않습니다. placeholder에서 문제가 발생했다면'std :: function' 대신'auto'를 사용하여 "bind-wrapper"유형을 추론 할 때 문제가 발생하지 않습니다. –

답변

16

std::function은 제공된 인수를 std::forward과 함께 랩핑 된 함수에 전달하도록 지정됩니다. 예 : std::function<void(MoveTracker)> 대한 함수 호출 연산자 std::forward<T> 이후

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

동등 T하지 참조 타입이 첫 번째 실시 예에서의 동작 중 하나를 차지하는 경우 std::move 동일하다. 두 번째는 간접 계층 인 std::function을 통과해야하는 것으로부터 가능할 수 있습니다.

이 다음 또한 래핑 된 함수로 std::bind를 사용하여 발생하는 문제에 대한 계정

: std::bind이 매개 변수를 전달하도록 지정도 이며,이 경우는 내부 std::forward 통화 의해 생기는를 rvalue 참조가 전달되는 std::function. 바인드 표현식의 함수 호출 연산자는 따라서 각 인수에 rvalue 참조를 전달합니다. 불행히도, 당신은 placeholder를 재사용했기 때문에, 두 경우 모두 동일한 객체에 대한 rvalue 참조이기 때문에, 먼저 움직이는 유형이 먼저 구성되면 값을 이동시키고 두 번째 매개 변수는 빈 쉘을 얻습니다.

+1

오, 나는'std :: forward '이'std :: move '와 동일하다는 것을 결코 깨닫지 못했습니다! 그것은 많은 것들을 설명합니다. –

관련 문제