想象一下以下代码:
void async(connection *, std::function<void(void)>);
void work()
{
auto o = std::make_shared<O>();
async(&o->member, [] { do_something_else(); } );
}
例如, async
将使用member
作为指针传递的o
启动一个线程。但是当o
在调用async()
之后超出范围并且它将被删除时会像这样编写,因此会被删除。
如何正确妥善地解决这个问题(!)?
显然one solution是将o
传递给捕获列表。即使不使用,也保证不会优化捕获。
async(&o->member, [o] { do_something_else(); } );
但是,最近的编译器(clang-5.0)包含-Wunused-lambda-capture
集合中的-Wextra
。这种情况会产生未使用的lambda-capture警告。
我在lamdba中添加了(void) o;
,这使这个警告无声。
async(&o->member, [o] {
(void) o;
do_something_else();
});
是否有更优雅的方法来解决这个范围问题?
(此问题的根源来自using write_async
of boost::asio
)
答案 0 :(得分:2)
Boost.Asio似乎suggest使用enable_shared_from_this
来保留&#34; connection
&#34;当有正在运行的操作时使用它。例如:
class task : std::enable_shared_from_this<task> {
public:
static std::shared_ptr<task> make() {
return std::shared_ptr<task>(new task());
}
void schedule() {
async(&conn, [t = shared_from_this()]() { t->run(); });
}
private:
task() = default;
void run() {
// whatever
}
connection conn;
};
然后使用task
:
auto t = task::make();
t->schedule();
这似乎是一个好主意,因为它封装了在task
本身内调度和执行task
的所有逻辑。
答案 1 :(得分:1)
我建议您的async
功能没有经过优化设计。如果async
在将来的某个任意点调用函数,并且它要求connection
在那时还活着,那么我会看到两种可能性。你可以做出任何拥有async
基础的逻辑也拥有connection
的逻辑。例如:
class task_manager {
void async(connection*, std::function<void ()> f);
connection* get_connection(size_t index);
};
这样,connection
在调用async
时将始终处于活动状态。
或者,您可以async
获取unique_ptr<connection>
或shared_ptr<connection>
:
void async(std::shared_ptr<connection>, std::function<void ()> f);
这比在关闭中捕获connection
的所有者更好,这可能会产生无法预料的副作用(包括async
可能期望connection
在函数对象之后保持活跃状态已被调用和销毁)。
答案 2 :(得分:0)
不是很好的答案,但是......
看起来似乎并不是更好的&#34; /&#34;更清洁&#34;解决方案,虽然我建议更多自我描述&#34;解决方案可能是为线程操作创建一个functor,它明确地绑定成员函数和其中的shared_ptr实例。使用虚拟lambda捕获并不一定能捕获意图,有人可能会在以后出现并且&#34;优化&#34;它结束了。不过,不可否认,将仿函数与shared_ptr绑定的语法稍微复杂一些。
我的2c,无论如何(我和我的建议相似,仅供参考)。
答案 3 :(得分:0)
我在a project of mine中使用的解决方案是从enable_shared_from_this
派生类,并在异步调用期间通过存储副本的数据成员让它泄漏共享指针
有关详细信息,请参阅Resource
class,特别是成员方法leak
和reset
清理后,它看起来像以下最小的例子:
#include<memory>
struct S: std::enable_shared_from_this<S> {
void leak() {
ref = this->shared_from_this();
}
void reset() {
ref.reset();
}
private:
std::shared_ptr<S> ref;
};
int main() {
auto ptr = std::make_shared<S>();
ptr->leak();
// do whatever you want and notify who
// is in charge to reset ptr through
// ptr->reset();
}
主要风险是,如果您从未重置内部指针,则会发生实际泄漏。在这种情况下,很容易处理它,因为底层库需要在丢弃它之前显式关闭资源,并在指针关闭时重置指针。在此之前,可以通过适当的函数(Loop
class的walk
成员函数)检索生命资源,仍然可以映射到底层库提供的内容,并且可以随时关闭它们,因此泄漏完全避免。
在你的情况下,你必须找到以某种方式避免问题的方法,这可能是一个问题,但它主要取决于实际的代码,我不能说。
一个可能的缺点是,在这种情况下,你被迫通过一个共享指针在动态存储上创建你的对象,否则整个事情就会爆发而不起作用。