如何将lambda函数传递给带有模板类的类实例化
How to pass a lambda function an instantiantion of a class with a template class
我试图创建一个包装POSIX timer functions
的c++类,以便我有一个计时器类,可以灵活地使用并传递用户定义的数据(通常不能直接使用POSIX C计时器函数)。所以我有以下内容:
Timer
类实现:
#include <functional>
#include <utility>
#include <sys/time.h>
#include <signal.h>
#include <time.h>
#include <string.h>
template<typename F>
class Timer
{
public:
struct sigaction SignalAction;
struct sigevent signalEvent;
struct itimerval timer_ms;
timer_t timerID;
Timer(F callback, int milliseconds) : onTimeout(std::move(callback))
{
timer_ms.it_value.tv_sec = milliseconds / 1000;
timer_ms.it_value.tv_usec = ( milliseconds % 1000 ) / 1000;
timer_ms.it_interval.tv_sec = milliseconds / 1000;
timer_ms.it_interval.tv_usec = ( milliseconds % 1000 ) / 1000;
// Clear the sa_mask
sigemptyset(&this->SignalAction.sa_mask);
// set the SA_SIGINFO flag to use the extended signal-handler function
this->SignalAction.sa_flags = SA_SIGINFO;
// Define sigaction method
// This function will be called by the signal
this->SignalAction.sa_sigaction = Timer::alarmFunction;
// Define sigEvent
// This information will be forwarded to the signal-handler function
memset(&this->signalEvent, 0, sizeof(this->signalEvent));
// With the SIGEV_SIGNAL flag we say that there is sigev_value
this->signalEvent.sigev_notify = SIGEV_SIGNAL;
// Now it's possible to give a pointer to the object
this->signalEvent.sigev_value.sival_ptr = (void*) this;
// Declare this signal as Alarm Signal
this->signalEvent.sigev_signo = SIGALRM;
// Install the Timer
timer_create(CLOCK_REALTIME, &this->signalEvent, &this->timerID);
sigaction(SIGALRM, &this->SignalAction, NULL);
}
void start()
{
// start the timer
//timer_settime(this->timerID, 0, &this->timerSpecs, NULL);
setitimer(ITIMER_REAL, &timer_ms, NULL);
return;
}
static void alarmFunction(int sigNumb, siginfo_t *si, void *uc)
{
// get the pointer out of the siginfo structure and asign it to a new pointer variable
Timer *ptrTimer = reinterpret_cast<Timer *> (si->si_value.sival_ptr);
// call the member function
ptrTimer->onTimeout();
}
private:
F onTimeout;
};
template<typename F>
Timer<F> CreateTimer(int milliseconds, F callback)
{
return Timer<F>(callback, milliseconds);
}
注意,在Timer类中,模板F onTimeout
在Timer::alarmFunction()
方法中通过使用存储在siginfo_t
结构体中的指针来调用。
static void alarmFunction(int sigNumb, siginfo_t *si, void *uc)
{
// get the pointer out of the siginfo structure and asign it to a new pointer variable
Timer *ptrTimer = reinterpret_cast<Timer *> (si->si_value.sival_ptr);
// call the member function
ptrTimer->onTimeout();
}
And my main.cpp:
#include "Timer.h"
#include <stdlib.h>
#include <iostream>
class Generic
{
private:
int m_data;
public:
Generic( int data ) : m_data( data ) {}
int getData() { return( m_data); }
};
void HandleTimer()
{
std::cout << "HandleTimer " << std::endl;
return;
}
int main(int argc, char *argv[])
{
Generic obj(42);
auto timer = CreateTimer(1000, [] { HandleTimer(); });
timer.start();
while(1)
{
sleep(5);
}
return( 0 );
}
在main.cpp中,您将看到创建了一个名为Generic
的愚蠢的小类,并实例化了一个名为obj
的类。
1-如何将obj
传递给HandleTimer()
:
auto timer = CreateTimer(1000, [] { HandleTimer(); });
这样当定时器被触发时,HandleTimer()
被Timer
类调用,并且可以访问obj
?
2-有什么更好的方法可以做到这一点?
我已经创建了一个简单得多的Timer类,它只接受频率和静态函数作为参数,以便在计时器到期时调用静态函数。这很好,但是静态函数不能访问任何类作用域,如果不借助于全局数据,就不能访问用户定义的数据。
编辑:我试过了:
void HandleTimer( Generic *obj )
{
std::cout << "HandleTimer " << std::endl;
return;
}
int main(int argc, char *argv[])
{
Generic obj(42);
auto timer = CreateTimer(1000, [&obj] { HandleTimer(&obj); });
timer.start();
while(1)
{
sleep(5);
}
return( 0 );
}
但是这会导致段错误。GDB会话如下:
(gdb) file blink
Reading symbols from /home/jrn/build_root/src/svn/arm/arm-gpio/Timer/blink...done.
(gdb) run
Starting program: /home/jrn/build_root/src/svn/arm/arm-gpio/Timer/blink
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
0x000000000040298a in __lambda0::operator() (__closure=0x100) at main.cpp:28
28 auto timer = CreateTimer(1000, [&obj] { HandleTimer(&obj); });
(gdb)
您的代码至少在两个不同的地方有未定义的行为。
首先,CreateTimer
函数按值返回Timer
对象。这意味着它正在复制计时器对象,并且由于您将this
指针传递给sigevent
,因此在复制之后,main()
中timer
对象的this
指针将不同。因此,当回调触发时,您将尝试在某个被销毁的对象上调用onTimeout
。
大多数情况下,您可能会侥幸逃脱,因为由于副本省略,不会生成副本。但是,您可以在修复其他错误后,通过使用-fno-elide-constructors
编译自己来验证错误行为。
您可以通过不使用CreateTimer()
来解决这个问题,而是将以下行添加到main()
中。我不知道有什么更好的办法来解决这个问题。这还演示了如何将obj
按值传递给lambda。
auto l = [obj]() { HandleTimer(obj); };
Timer<decltype(l)> timer(l, 1000);
timer.start();
还要考虑让你的类不可复制/不可赋值
Timer(Timer const&) = delete;
Timer& operator=(Timer const&) = delete;
第二个问题也是与this
指针,但我不知道足够的定时器你用来找出你做错了什么。将这些调试语句添加到代码中:
// Now it's possible to give a pointer to the object
this->signalEvent.sigev_value.sival_ptr = static_cast<void *>(this);
std::cout << "this: " << this << std::endl;
和
Timer *ptrTimer = static_cast<Timer *> (si->si_value.sival_ptr);
std::cout << "sival_ptr: " << si->si_value.sival_ptr << std::endl;
// call the member function
// ptrTimer->onTimeout();
您将看到两个语句打印的指针值是不同的。查看您注释掉的一些代码,以及这个答案,我做了以下更改:
添加数据成员并在构造函数中初始化
struct itimerspec timerSpecs;
...
timerSpecs.it_value.tv_sec = milliseconds / 1000;
timerSpecs.it_value.tv_nsec = ( milliseconds % 1000 ) / 1000; // this needs fixing
timerSpecs.it_interval.tv_sec = milliseconds / 1000;
timerSpecs.it_interval.tv_nsec = ( milliseconds % 1000 ) / 1000; // this needs fixing
...
void start()
{
// start the timer
timer_settime(this->timerID, 0, &this->timerSpecs, NULL);
// setitimer(ITIMER_REAL, &timer_ms, NULL);
return;
}
还稍微修改了HandleTimer
void HandleTimer(Generic g)
{
std::cout << "HandleTimer " << g.getData() << std::endl;
return;
}
现在你的代码运行并输出
HandleTimer 42
您可以像这样通过引用将obj传递给lambda:
auto timer = CreateTimer(1000, [&obj] { HandleTimer(obj); });
请注意,这会在堆栈上创建对对象的引用,这对于异步回调(如计时器)是危险的,因为计时器可能在对象离开作用域后回调到lambda。
我经常使用std::shared_ptr<>,因为这个原因:
#include <memory>
// ...
std::shared_ptr<Generic> obj(new Generic(42));
auto timer = CreateTimer(1000, [obj] { HandleTimer(obj); });
lambda(和计时器)将在计时器的生命周期内保存对obj的引用。
考虑用std::function代替你自己的模板形参作为回调函数。
#include <functional>
// ...
Timer(std::function<void()> callback, int milliseconds): onCallback(callback), // etc
正如我在评论中提到的,我认为使用std::chrono
和std::thread
可能会简单得多。具体来说,你可以这样写:
#include <chrono>
#include <iostream>
#include <thread>
// Generic class and HandleTimer code goes here
int main()
{
Generic obj(42);
std::thread([&obj]() {
while(1) {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
HandleTimer(obj);
}
}).detach();
while(1)
{
++obj;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
return 0;
}
请注意,std::thread
中的lambda通过引用捕获obj
,因此您必须注意它不会比传递的对象活得更长,但是这段代码正确地处理了这种情况。
std::thread
将每1000毫秒调用HandleTimer(obj)
(或您希望的任何函数)。
我已经修改了你的Generic
对象,以添加一个自增运算符,并使getData()
const:
Generic &operator++() { ++m_data; return *this; }
int getData() const { return m_data; }
我也改变了你的HandleTimer
例程,像这样:
void HandleTimer(const Generic &obj)
{
std::cout << "HandleTimer " << obj.getData() << std::endl;
}
在主循环中,每5秒调用一次。结果是HandleTimer将每秒被调用一次,并且它所指向的对象每5秒增加一次。当我运行程序时,我得到这样的输出,每秒打印一行。
HandleTimer 43
HandleTimer 43
HandleTimer 43
HandleTimer 43
HandleTimer 44
HandleTimer 44
HandleTimer 44
HandleTimer 44
HandleTimer 44
HandleTimer 45
HandleTimer 45
HandleTimer 45
HandleTimer 45
...
请注意,如果您不想在其他线程也打印时导致输出混乱,那么确实应该有一个互斥锁来保护对std::cout
的访问,但我将把这个问题留给您。
这个解决方案的好处是它是可移植的,而不依赖于Posix计时器的神秘细节,正如您所看到的,这些细节对于正确使用是非常重要的。
- 从C++实例化QML
- 设计一个只能由特定类实例化的类(如果可能的话,通过make_unique)
- 如何创建一个空的全局类并在启动时实例化它
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 约束和显式模板实例化
- 为什么包含windows.h会产生语法错误,从而阻止类的实例化?(C2146,C2065)
- 对象实例化调用构造函数的次数太多
- 如何使用非默认构造函数实例化模板化类
- 静态数据成员模板专用化的实例化点在哪里
- 错误的cv::face FacemarkLBF实例化
- C++的解析器在可以区分比较和模板实例化之前会做什么?
- 为什么 gcc 和 clang 为函数模板的实例化生成不同的符号名称?
- 是否未指定在未评估的上下文中实例化模板/lambda
- Lambda构造函数中的实例化
- 模板,lambda作为每个实例化的唯一默认参数
- C++-为任何lambda创建一个实例化桶
- 在泛型lambda表达式的所有实例化之间共享的局部静态变量
- 按类型实例化C++ lambda
- 如何将lambda函数传递给带有模板类的类实例化
- 由于抽象模板arg的实例化,Boost::lambda表达式编译失败.任何解释和/或解决方法