2015-01-20 3 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 :: объект функции и шаблоны с параметром «вызываемый»

Вопрос2. Потому что я не понимаю, как работает код в (*), возникает второй вопрос. Должен ли я гарантировать, что каждая из переменных bound_f1 и bound_f2 будет жить до тех пор, пока не завершится соответствующий поток, созданный async_g()?

Вопрос3. Почему раскомментирование строки, обозначенной (**), вызывает ошибку компиляции?

+3

Пожалуйста, отредактируйте ваш вопрос, включив код, указанный в (**), и ошибку компиляции _exact_, которую вы получите. –

+0

Можете ли вы узнать, что «Callable» находится в первом вызове async_g? например используя '__PRETTY_FUNCTION__' внутри него, чтобы узнать тип fn. Я думаю, что ответ на (1) лежит в нем. – greggo

+2

1 и 2: читайте о универсальных ссылках. – ForEveR

ответ

5

Короткий ответ: В контексте типа шаблона дедукции, где тип будучи выведенной из выражения формы

template <typename T> 
T&& t 

т не является ссылкой на Rvalue но ссылка экспедиторская (ключевое слово поиск, иногда также называемый универсальной ссылкой). Это происходит также для автоматического типа удержания

auto&& t = xxx; 

Что ссылки экспедиционных делают, они связываются с обеими Левой и правой ссылкой, и только на самом деле предназначены для использования с std::forward<T>(t) для пересылки параметров с тем же ссылочным отборочными к следующему функция.

При использовании этой универсальной ссылки с Lvalue, тип выводится для T является type&, в то время как при использовании его с ссылкой RValue тип будет просто type (сводится к тому, ссылки рушатся правила). Итак, теперь давайте посмотрим, что происходит в ваших вопросах.

  1. Ваша async_g функция вызывается с bound_f1, которая именующее выражение. Таким образом, тип, выводимый для Callable, является std::function<void(int)>&, и поскольку вы явно передаете этот тип g, g ожидает параметр типа lvalue. Когда вы вызываете bind, он копирует аргументы, которые он связывает, поэтому fn будет скопирован, и эта копия будет передана в g.

  2. bind (и thread/async) выполняет копии/шаги аргументов, и если вы думаете об этом, это правильная вещь. Таким образом, вам не нужно беспокоиться о жизни bound_f1/bound_f2.

  3. Так как вы на самом деле прошли RValue в вызов async_g, на этот раз типа вывел для Callable просто std::function<void(int)>. Но поскольку вы перенаправили этот тип на g, он ожидает аргумент rvalue. Хотя тип fn имеет значение rvalue, он сам является значением lvalue и копируется в bind. Поэтому, когда функция привязки выполняется, она пытается позвонить

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

    с аргументом, который не является значением rvalue. И вот откуда исходит ваша ошибка.В 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 с перегруженным opeartor()? – PowerGamer