Похоже, что и порядок оценки аргументов функции , и порядок инициализаторов лямбда-захвата , не указан по стандарту C ++.
(см. http://en.cppreference.com/w/cpp/language/lambda, а также Порядок вычисления в параметрах функции C ++ )
Это вызывает у меня некоторую обеспокоенность из-за того, как оно может взаимодействовать с семантикой перемещения.
Предположим, у меня есть объект типа T
, который может иметь конструктор копирования или перемещения, который выдает.Тогда предположим, что у меня есть объект только для перемещения, такой как std::promise
.Рассмотрим следующую ситуацию:
T value; // some type that potentially throws when moved or copied
promise<U> pr; // a promise whose result is some type U
future<U> fut = pr.get_future();
std::thread(
[v = std::move(value), pr = std::move(pr)]() {
try {
// do some stuff
pr.set_value(/* whatever */);
}
catch (...) { pr.set_exception(std::current_exception()); }
}
).detach();
// return the future
Теперь у нас есть блок try / catch, который выполняется внутри std::thread
, но у нас нет обработки исключений для всего, что могло бы пойти не так при инициализациинить.В частности, что мы можем сделать, если выражение v = std::move(value)
в списке лямбда-захвата в итоге выдает исключение?В идеале, мы бы хотели обработать его с помощью блока try-catch, а затем просто позвонить pr.set_exception(...)
, например:
try {
std::thread(
[v = std::move(value), pr = std::move(pr)]() {
try {
// do some stuff
pr.set_value(/* whatever */);
}
catch (...) { pr.set_exception(std::current_exception()); }
}
).detach();
}
catch (...) {
pr.set_exception(std::current_exception());
}
Есть только одна основная проблема: когда мы получимчто касается нашего внешнего блока catch, мы не знаем, было ли уже вызвано выражение pr = std::move(pr)
, потому что у нас нет никакой гарантии относительно порядка для списка инициализаторов лямбда-захвата.Поэтому, когда мы говорим pr.set_exception(...)
, мы больше не знаем, является ли наше обещание даже действительным , потому что мы не знаем, было ли это обещание построено на ходу до выражения v = std::move(value)
был оценен.
Так, как мы можем обработать случай, когда конструктор перемещения или копирования для T
может выдать?
Единственное решение, которое я могу придумать - может быть - это обернутьлямбда в вызове std::bind
, например:
std::thread(
std::bind(
[v = std::move(value)](promise<U>& pr) {
// ...
},
std::move(pr)
)
).detach();
Здесь, даже если у нас нет никаких гарантий относительно порядка вычисления аргументов функции,Насколько я понимаю, мы по-прежнему гарантируем, что выражение v = std::move(value)
необходимо будет оценить до того, как обещание будет действительно построено, поскольку выражение std::move(pr)
фактически не перемещается, создает обещание - оно просто приводит его к R-значение.Обещание будет сконструировано только позже, внутри вызова std::bind
, но , а не как результат одного из оцениваемых аргументов функции.
Однако я не совсем уверен в этом решении.Я не уверен, может ли стандарт каким-либо образом разрешить компилятору перемещать-создавать обещание до того, как T
будет создано с помощью перемещения / копирования.
Итак, мое решение, использующее std::bind
, решает эту проблему?Если нет, как можно решить эту проблему?