2015-01-20 2 views
1
#include <functional> 
#include <future> 

void z(int&&){} 
void f1(int){} 
void f2(int, double){} 

template<typename Callable> 
void g(Callable&& fn) 
{ 
    fn(123); 
} 

template<typename Callable> 
std::future<void> async_g(Callable&& fn) 
{ 
    return std::async(std::launch::async, std::bind(&g<Callable>, fn)); 
} 

int main() 
{ 
    int a = 1; z(std::move(a)); // Does not work without std::move, OK. 

    std::function<void(int)> bound_f1 = f1; 
    auto fut = async_g(bound_f1); // (*) Works without std::move, how so? 
    // Do I have to ensure bound_f1 lives until thread created by async_g() terminates? 
    fut.get(); 

    std::function<void(int)> bound_f2 = std::bind(f2, std::placeholders::_1, 1.0); 
    auto fut2 = async_g(bound_f2); 
    // Do I have to ensure bound_f2 lives until thread created by async_g() terminates? 
    fut2.get(); 

    // I don't want to worry about bound_f1 lifetime, 
    // but uncommenting the line below causes compilation error, why? 
    //async_g(std::function<void(int)>(f1)).get(); // (**) 
} 

질문 1.std::move없이 (*)의 통화가 작동하는 이유는 무엇입니까?std :: async, std :: function 개체 및 템플릿을 '호출 가능'매개 변수가 포함 된

질문 2. (*) 코드가 어떻게 작동하는지 이해할 수 없기 때문에 두 번째 질문이 발생합니다. async_g()에 의해 생성 된 해당 스레드가 종료 될 때까지 각각 bound_f1bound_f2 변수가 살아 있는지 확인해야합니까?

질문 3. 왜 (**)로 표시된 줄의 주석 처리에서 컴파일 오류가 발생합니까?

+3

(**)로 표시된 코드와 _exact_ 컴파일 오류가 포함되도록 질문을 편집하십시오. –

+0

async_g의 첫 번째 호출에서 '호출 가능'이 무엇인지 확인할 수 있습니까? 예 : 그 안에'__PRETTY_FUNCTION__'을 사용하여, fn의 타입을 찾아냅니다. 나는 (1)에 대한 답이 그 안에 있다고 생각한다. – greggo

+2

1 및 2 : 범용 참조에 대해 읽어보십시오. – ForEveR

답변

5

짧은 답변 : 유형이 형태

template <typename T> 
T&& t 

t의 표현에서 도출되는 템플릿 유형 공제의 맥락에서 는를 rvalue 참조하지만 전달 참조 (키워드 아니다 찾아보기, 때로는 범용 참조라고도 함). 이것은 또한 자동 유형 공제 발생

auto&& t = xxx; 
전달 참조들이 모두 좌변과를 rvalue 참조 결합이다 수행 만 정말 다음에 동일한 참조 한정자 매개 변수를 전달하는 std::forward<T>(t)으로 사용되는 의미가 무엇

기능. 당신이를 rvalue 참조와 함께 사용할 때 유형은 단지 type 될 것입니다 반면

당신이 좌변과이 보편적 참조를 사용

T에 대한 추론 유형 (붕괴 규칙을 참조하는 온다), type&입니다. 자 이제 질문에서 어떤 일이 일어나는지 봅시다.

  1. 귀하의 async_g 기능은 좌변입니다 bound_f1 호출합니다. 따라서 Callable에 대해 추론 된 유형은 std::function<void(int)>&이며이 유형을 명시 적으로 g으로 전달하므로 g에는 lvalue 유형의 매개 변수가 필요합니다. bind을 호출하면 바인딩 된 인수가 복사되므로 fn이 복사되고이 복사본은 g으로 전달됩니다.

  2. (및 스레드/비동기)은 인수의 복사/이동을 수행하며, 생각해 보면 이것이 올바른 것입니다. 그렇게하면 평생 걱정할 필요가 없습니다 bound_f1/bound_f2.

  3. async_g에 대한 통화에 실제로 rvalue를 전달 했으므로 Callable에 대해 추론 된 유형은 이번에는 std::function<void(int)>입니다. 그러나이 유형을 g으로 전달했기 때문에 rvalue 인수가 필요합니다. fn 유형은 rvalue이지만 그 자체는 lvalue이며 bind에 복사됩니다. 바운드 함수가 실행 때, 그것은를 rvalue 아닌 인수로

    void g(std::function<void(int)>&& fn) 
    

    를 호출하려고합니다. 그리고 그것이 여러분의 실수가 어디서 오는가입니다.VS13에서 최종 오류 메시지는 다음과 같습니다

    Error 1 error C2664: 'void (Callable &&)' : 
    cannot convert argument 1 from 'std::function<void (int)>' to 'std::function<void (int)> &&'  
    c:\program files\microsoft visual studio 12.0\vc\include\functional 1149 
    

지금 당신은 실제로 당신이 전달할 필요가 얼마나 멀리, (Callable&&)를 전달 참조를 사용하여 달성하기 위해 노력하고 어디서 인수가되어있는 것을 다시 생각한다 끝까지. 이것은 논쟁의 수명에 대해서도 생각할 필요가있다.

오류를 극복하려면 bind을 람다로 바꾸는 것으로 충분합니다 (항상 좋은 생각입니다!). 코드는 다음과 같습니다.

template<typename Callable> 
std::future<void> async_g(Callable&& fn) 
{ 
    return std::async(std::launch::async, [fn] { g(fn); }); 
} 

최소 노력이 필요한 솔루션이지만 인수가 람다에 복사됩니다.

+0

당신이 정확하게 설명했던 이론을 이해했는지 확인하기 위해서 : 나는 당신의 람다를'[&] {g (fn);}로 바꾼다면'bound_f1','bound_f2'의 수명과 왜 걱정해야합니까? – PowerGamer

+0

나는 네가 일생 일대일에 대해 걱정해야한다고 말할 것이다. 그러나 나는 단지 97 % 정도의 확신을 가지고있다. 'fn'가 임시로 묶여 있다면,'async_g'를 남겨두면 임시는 죽습니다. 이것은 람다가 더 이상 존재하지 않는 변수에 대한 참조를 가지고 있음을 의미합니다. 'fn'가 lvalue에 바인드 된 경우, 리턴 된 미래를 쓰레드간에 전달할 수 있으며, 미래를 생성 한 쓰레드는'fn'을 사용하여 종료합니다. 따라서 병렬 환경에서 참조로 전달하는 것에 대해 매우주의해야합니다. – jepio

+0

해결책은'async_g (f1);'에 대해 작동하지 않습니다. 'async_g'와'g'를 어떻게 수정하여 일반 함수처럼 호출 할 수있게 만들 수 있습니까?'std :: bind', lambdas, structs가 overload 된 opeartor()입니까? – PowerGamer