lamda本质
lamda的本质是一个函数对象,并且捕获的变量,都是这个函数对象的成员变量。
所以,按值捕获,相当于把值拷贝进了这个闭包里面,所以即使这个lamda不实际执行,这个值也从捕获的那一刻起就确定下来了。
包括捕获shared_ptr也是,引用计数的增加,也是在捕获的那一刻就加一了,不管这个lamda有没有实际的执行。
注意,lamda本质上的那个函数对象,所包含的仿函数是const的,也就是,捕获的所有的变量都不允许在lamda执行体内被修改,这个限制,可以再lamda声明的时候,在{前面加上mutable)解决
有时候 std::move(a), 但是发现并没有调用到移动构造函数,而是调用到了拷贝构造函数,就是因为a 是const类型的,所以move后转成了const A&&,所以调用不到 A&& 为形参的移动构造函数。
mutable同样可以修饰某个变量,这样这个变量在const成员函数中就可以继续被修改。
有时候需要用到移动捕获,也就是 [a = std::move(a)], 移动的时机也是在lamda表达式创建的时候,就移动了,不是在lamda表达式执行的时候。
lamda和值捕获shared_ptr配合,有时候会有内存泄漏,这个泄漏和循环引用导致的内存泄漏不同,
{
std::shared_ptr<A> sp1 = std::make_shared<A>();
m_f = [sp1](){...;};
}
如果上述代码被频繁调用,而m_f又没有被真正调用过,则每次调用上述代码,sp1的引用计数都是1(被m_f 持有),所以调用的一多,内存就涨了,但是其实是还有释放时机,只是没有被调用到而已。
function, lamda 和 C风格函数指针之间的关系
Function 和 lamda 由于可以捕获,所以相当于在C风格函数指针上,增加一个指向自己数据的指针,自己的数据存在栈上,所以理论上,无法从function& lamda转成C风格函数指针。
但是,仍然有一些“办法”从C++转成C风格:
-
如果lamda 是如下写法,不带捕获的,则直接就可以转成函数指针:
auto lda = [](int64_t samplePerChannel, void* frameData, int64_t context) -> void {
ByteviewFormatRequester::GetInstance()->SetWriterFrameData(frameData, (size_t)samplePerChannel,
(int64_t)context);
};
注意,一定不要捕获,否则转换会报错。
-
std::function 如果用函数指针初始化,比如:
template<typename T, typename... U> size_t getAddress(std::function<T(U...)> f) { typedef T(fnType)(U...); fnType ** fnPointer = f.template target<fnType*>(); return (size_t) *fnPointer; }
typedef void (*PFNOnRecordingData)(int64_t samplePerChannel, void* buffer, int64_t timeStampMs);
void testfun(int64_t samplePerChannel, void* buffer, int64_t timeStampMs) {}
std::function<void(int64_t samplePerChannel, void* frameData, int64_t context)> f = testfun;
这种情况下,可以用target方法转换成函数指针:getAddress(f)
其他情况下,无法转换成函数指针。
-
如果真需要捕获,怎么办?
可以用static对象,这种对象不用捕获:
static std::string pcmFile = "";
auto lda = [](int64_t samplePerChannel, void* frameData, int64_t context) -> void {
pcmFile = "a";
ByteviewFormatRequester::GetInstance()->SetWriterFrameData(frameData, (size_t)samplePerChannel,
(int64_t)context);
};
这种情况下lda仍然可以转成函数指针!!
std的异步执行promise, future, packaged_task, async区别,以及如何转成std::function<void()> ?
-
先从promise何future说起,如果有一个函数,希望它异步执行(在另一个线程执行),并且可以同步的阻塞的拿到这个函数的执行结果(这个很关键,这几个关键字都是围绕这个阻塞的结果的,如果不需要一个阻塞的结果,那直接:std::thead th(fun), th.detach(),就可以了,不需要这么麻烦)
首先在执行之前,把promise和future“绑定"起来,也就是要通过future.get()拿结果,那么要在真正的执行前,先建立一个绑定关系:std::future<> fut = pr.get_future();
然后,要在执行的线程中(要再写一个std::thread,然后在执行函数的末尾设一下 pr.set_value(vvv);
fut.get() 在set_value之后,会立即的收到结果。
可见:如果用 condition_variable, std::mutex,加一个类的成员变量,也能实现这个功能。
-
packaged_task = promise + function,packaged_task本身是一个不可复制,只能移动的对象,这个对象有get_future方法(对应promise.get_future()), 并且可以重载 opeator() 执行(注意,如果要异步执行,仍然需要手动写一个std::thread, 并且在thread的执行函数中 用 opeator() 去执行packaged_task 包装的方法,可见这packaged_task 本身没有异步能力。
-
async就等于 packaged_task + thread了,在async的构造函数中,传递一个packaged_task 或funtion,就能直接异步执行(内部起个线程),注意可以懒启动,就是调用get的时候才启动线程
-
async和packaged_task 都返回一个future,可见future是比promise用处更广的,promise基本上可以不用了
问题来了:如果有个void postTask(std::function<void()> fun), 可以放任务到这个线程池里面,如果还想阻塞的拿结果,怎么弄?
经过反复试验,一个packaged_task 永远无法转成std::function<void()>,哪怕用move捕获:比如
std::packaged_task<> patask = xxxx.; postTask([pa = std::move(patask)](){pa();}); 这样也会报错,目前只有一个猜测,就是function是可以复制的,但是packaged_task是无法复制的,所以不能转换。
有没有办法呢?有,用智能指针!!
std::shared_ptr<std::packaged_task<xxx>> shaptr = std::make_shared<std::packaged_task<xxx>>(std::move(patask)); 然后postTask([shaptr](){(*shaptr)();} 注意:转成shared_ptr时一定要move!,一方面不move会报错,另一方面,move后,把原来packaged_task完全接管,这样就不存在生命周期的问题。
另外,提一个lamda编译容易出错的解决方案:lamda默认都是const捕获,所以如果在lamda里面改捕获的值可能会报错,加上mutable关键字即可。