这就是为什么这个代码工作的问题。我想知道如何修复命名空间dj中的代码,而不是在演示程序中。您可能希望在进一步阅读之前运行该程序。
当我通过rvalue std::string
通过std::thread
时,字符串到达并行函数为空。也许原始值已经被std :: move,但最终出现在了错误的地方。但我认为问题可能在于函数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();
}
我无法弄清楚如何捕捉lambda的东西。 &符吓到了我,但我无法解决它。我已经尝试过使用bind
或function
而不是lambda。没有快乐。
...
演示程序......主线程开始暂停一段给定的秒数。然后新线程打印一个字符串并发出蜂鸣声,直到主线程将原子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;
}
答案 0 :(得分:4)
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();
}
这导致未定义的行为,因为引用捕获的数据销毁相对于线程中的代码没有排序。
永远不要在lambda上使用&
,其生命周期(或其副本)比当前范围更长。周期。
你的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();
}
请注意,此结果将所有内容复制到线程中。如果您确实想要传递左值引用,请使用std::ref
,这主要使它适用于您。
在c++14或c++11中,您最好的解决方案是编写自己的notstd::apply
。有很多人写过这篇文章,包括myself here。
注意我在lambda中使用了[&]
; lambda并不比当前范围更长(实际上它并不比当前的线更长)。这是您应该使用的唯一情况[&]
。