C++ function, lamda 和 std::future,C函数指针之间的关系

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风格:

  1. 如果lamda 是如下写法,不带捕获的,则直接就可以转成函数指针:

auto lda = [](int64_t samplePerChannel, void* frameData, int64_t context) -> void {

ByteviewFormatRequester::GetInstance()->SetWriterFrameData(frameData, (size_t)samplePerChannel,

(int64_t)context);

};

注意,一定不要捕获,否则转换会报错。

  1. 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)

其他情况下,无法转换成函数指针。

  1. 如果真需要捕获,怎么办?

可以用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()> ?

  1. 先从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,加一个类的成员变量,也能实现这个功能。

  1. packaged_task = promise + function,packaged_task本身是一个不可复制,只能移动的对象,这个对象有get_future方法(对应promise.get_future()), 并且可以重载 opeator() 执行(注意,如果要异步执行,仍然需要手动写一个std::thread, 并且在thread的执行函数中 用 opeator() 去执行packaged_task 包装的方法,可见这packaged_task 本身没有异步能力。

  2. async就等于 packaged_task + thread了,在async的构造函数中,传递一个packaged_task 或funtion,就能直接异步执行(内部起个线程),注意可以懒启动,就是调用get的时候才启动线程

  3. 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关键字即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值