Это вопрос, почему этот код не работает. Я хочу знать, как исправить код в пространстве имен dj, а не в демонстрационной программе. Возможно, вы захотите запустить программу, прежде чем читать дальше.
Когда я передаю rvalue std::string через std::thread, строка попадает в параллельную функцию пустой. Возможно, исходное значение было std :: перемещено, но оказалось не в том месте. Но думаю, проблема скорее всего в функции timer. Там я думаю, что строка захватывается ссылкой, и когда rvalue исчезает, ссылка недействительна.
// In the demo program, the string is the one element of `...args`.
template<typename F, typename... Args>
void timer(double seconds, F& f, Args&&... args) {
auto th = std::thread(
[&, seconds] { delayed(seconds, f, std::forward<Args>(args)...); });
th.detach();
}
Я не могу понять, как записывать вещи для лямбды. Меня пугает амперсанд, но я не смог его обойти. Я делал различные попытки использовать bind или function вместо лямбда. Никакой радости.
...
Демо-программа ... Основной поток запускает шаг, который приостанавливается на заданное количество секунд. Затем новый поток печатает строку и подает звуковой сигнал, пока основной поток не установит для атомарного bool значение true. Чтобы показать, что он не работает, установите глобальный bool demo_the_problem в значение true. Строка поступает в функцию сигнализации пустой.
#include <thread>
#include <chrono>
#include <iostream>
static bool demo_the_problem = false;
namespace dj {
inline void std_sleep(long double seconds) noexcept
{
using duration_t = std::chrono::duration<long long, std::nano>;
const auto duration = duration_t(static_cast<long long> (seconds * 1e9));
std::this_thread::sleep_for(duration);
}
// Runs a command f after delaying an amount of time
template<typename F, typename... Args>
auto delayed(double seconds, F& f, Args&&... args) {
std_sleep(seconds);
return f(std::forward<Args>(args)...);
}
// Runs a function after a given delay. Returns nothing.
template<typename F, typename... Args>
void timer(double seconds, F& f, Args&&... args) {
auto th = std::thread( // XXX This appears to be where I lose ring_tone. XXX
[&, seconds] { delayed(seconds, f, std::forward<Args>(args)...); });
th.detach();
}
}
using namespace dj;
int main() {
std::atomic<bool> off_button(false);
// Test dj::timer, which invokes a void function after a given
// period of time. In this case, the function is "alarm_clock".
auto alarm_clock = [&off_button](const std::string ring_tone) {
char bel = 7;
std::cout << ring_tone << '\n';
while (!off_button) {
std_sleep(0.5);
std::cout << bel;
}
off_button = false;
};
auto ring = std::string("BRINNNNGGG!");
if (demo_the_problem)
timer(4.0, alarm_clock, std::string("BRINNNNGGG!")); // Not OK - ring arrives as empty string
else {
timer(4.0, alarm_clock, ring); // Ring tone arrives intact.
}
// Mess around for a while
for (int i = 0; i < 12; ++i) {
if (i == 7) {
off_button = true; // Hit the button to turn off the alarm
}
std::cout << "TICK ";
std_sleep(0.5);
std::cout << "tock ";
std_sleep(0.5);
}
// and wait for the button to pop back up.
while(off_button) std::this_thread::yield();
std::cout << "Yawn." << std::endl;
return 0;
}
@Slava - Надеюсь, что ничего не зависит от "фичи" MS. Отключаю все, что могу.





template<typename F, typename... Args>
void timer(double seconds, F& f, Args&&... args) {
auto th = std::thread(
[&, seconds] { delayed(seconds, f, std::forward<Args>(args)...); });
th.detach();
}
это приводит к неопределенному поведению, поскольку уничтожение контрольных захваченных данных не упорядочено относительно кода в потоке.
Никогда и никогда не используйте & с лямбдой, время жизни которой (или ее копий) превышает текущую область видимости. Период.
Ваш detach также является запахом кода; не существует практического способа определить, завершается ли поток до завершения main, а потоки, которые переживают основной, имеют неопределенное поведение. Это C++, вы несете ответственность за очистку использования ресурсов. Найдите для этого решение. Я пока проигнорирую это.
template<typename F, typename... Args>
void timer(double seconds, F&& f, Args&&... args) {
auto th = std::thread(
[seconds,
f=std::forward<F>(f),
tup=std::make_tuple(std::forward<Args>(args)...)
]
{
// TODO: delayed(seconds, f, std::forward<Args>(args)...);
}
);
th.detach();
}
Теперь нам просто нужно записать строку // TODO.
В C++ 17` это просто.
template<typename F, typename... Args>
void timer(double seconds, F&& f, Args&&... args) {
auto th = std::thread(
[seconds,
f=std::forward<F>(f),
tup=std::make_tuple(std::forward<Args>(args)...)
]() mutable
{
std::apply(
[&](auto&&...args){
delayed(seconds, f, decltype(args)(args)...);
},
std::move(tup)
);
}
);
th.detach();
}
Обратите внимание, что в результате копии все попадает в поток. Если вы действительно, действительно хотите передать ссылку lvalue, используйте std::ref, который в основном заставляет его работать на вас.
Лучшее решение в C++ 14 или C++ 11 - написать собственный notstd::apply. Есть много людей, которые написали это, в том числе я здесь.
Обратите внимание, я использовал [&] в лямбде; эта лямбда не переживает текущую область видимости (фактически, она не переживает текущую строку). Это единственная ситуация, в которой вы должны когда-либо использовать [&].
Спасибо. Об этом есть над чем подумать. По поводу запаха. Это духи. Поток показывает, когда он завершен, с помощью атомарного логического значения. Посмотрите внизу демонстрационной программы.
«Обратите внимание, что этот результат копирует все в поток. Если вы действительно, действительно хотите передать ссылку lvalue, используйте std :: ref, что в основном заставляет его работать на вас». Для пользователя было бы разумно захватить атомарную переменную по ссылке при реализации вызываемой функции. Вы подразумеваете, что пользователь должен знать о требованиях и о том, как использовать std :: ref?
@jive неограниченное количество времени может быть потрачено в потоке между установкой этого bool и фактическим завершением потока. Как в теории (без шитья), так и на практике (код, уничтожающий захваченные данные, запускается после установки bool). Это не духи, это навоз. Научитесь не использовать отсоединение.
Хорошо. Покажи мне учителя. Я хочу знать, как это сделать правильно. Я уже знаю, как это сделать неправильно.
@jive Это C++, ответственность за время жизни и право собственности лежит на каждом. Скрытие зависимостей времени жизни приведет к поломке вашей программы или, если она сработает, сработает случайно. Вы не добавляете зависимости времени жизни между потоками только потому, что кто-то передает lvalue. По умолчанию копируйте вещи между потоками (или полностью передавайте владение), максимально ограничивайте совместное использование. Доставьте данные обратно по контролируемым каналам, таким как потокобезопасные очереди, фьючерсы или перекачки событий. Поток - жесткий. Ваш подход приведет к тому, что программы будут работать в лучшем случае случайно.
Если бы я думал, что мой подход правильный, я бы не задавал этот вопрос.
@JiveDadson Пропавший ;? живой пример.
В C++ 20 еще проще! [seconds, f=forward<F>(f), ...args=forward<Args>(args)]() mutable { delayed(seconds, f, forward<Args>(args)...); }
@barry Ваш второй forward не тот, он должен быть std::move, я думаю?
@Yakk Нет, я не О :-)
@ Барри Не что?
@Yakk Хахаха. Это будет так запутать будущих читателей. В любом случае, я думаю, что forward прав, если они изначально lvalue.
@Barry не захватывает ...args по значению, а не по ссылке? Вы должны относиться к ним как к их ценностной категории, а не как к их происхождению.
@Yakk Да, ты прав. Тем более, что нам нужно взять на себя ответственность.
Еще раз спасибо, Якк. Намного лучше. Я все еще не понимаю, какой поток запускается после основных выходов. Я бы подумал, что если недостаточно, чтобы сигнал тревоги очищал атомарный объект, а основной поток ожидает его очистки, тогда любое использование detach вызовет UB. Если да, то почему он существует? Это вопрос для другого Вопроса.
Вы уверены, что временный
std::stringпередан как rvalue? Здесь может помочь "функция" надоедливого MS по продвижению temp до lvalue.