我使用的几个代码库包括以下列模式手动调用new
和delete
的类:
class Worker {
public:
void DoWork(ArgT arg, std::function<void()> done) {
new Worker(std::move(arg), std::move(done)).Start();
}
private:
Worker(ArgT arg, std::function<void()> done)
: arg_(std::move(arg)),
done_(std::move(done)),
latch_(2) {} // The error-prone Latch interface isn't the point of this question. :)
void Start() {
Async1(<args>, [=]() { this->Method1(); });
}
void Method1() {
StartParallel(<args>, [=]() { this->latch_.count_down(); });
StartParallel(<other_args>, [=]() { this->latch_.count_down(); });
latch_.then([=]() { this->Finish(); });
}
void Finish() {
done_();
// Note manual memory management!
delete this;
}
ArgT arg_
std::function<void()> done_;
Latch latch_;
};
现在,在现代C ++中,显式delete
是一种代码气味,在某种程度上是delete this
。但是,我认为这种模式(创建一个表示由回调链管理的大量工作的对象)从根本上说是一个好的,或者至少不是一个坏的想法。
所以我的问题是,我应该如何重写这种模式的实例以封装内存管理?
我不认为的一个选项是一个好主意是将Worker
存储在shared_ptr
中:从根本上说,所有权不在此处共享,因此开销很大引用计数是不必要的。此外,为了在回调中保留shared_ptr
活着的副本,我需要继承enable_shared_from_this
,并记得在之外调用 lambdas并捕获shared_ptr
进入回调。如果我直接使用this
编写简单代码,或者在回调lambda中调用shared_from_this()
,则可以提前删除该对象。
答案 0 :(得分:3)
我同意delete this
是代码气味,并且在较小程度上delete
。但我认为这是延续传递风格的自然部分,对我而言,它本身就是一种代码气味。
根本问题是这个API的设计假设无限制的控制流:它确认调用者对调用完成时发生的事情感兴趣,但是通过任意复杂的回调表示完成,而不是简单地从同步通话。最好同步构建它,让调用者确定适当的并行化和内存管理机制:
class Worker {
public:
void DoWork(ArgT arg) {
// Async1 is a mistake; fix it later. For now, synchronize explicitly.
Latch async_done(1);
Async1(<args>, [&]() { async_done.count_down(); });
async_done.await();
Latch parallel_done(2);
RunParallel([&]() { DoStuff(<args>); parallel_done.count_down(); });
RunParallel([&]() { DoStuff(<other_args>); parallel_done.count_down(); };
parallel_done.await();
}
};
在来电者方面,它可能看起来像这样:
Latch latch(tasks.size());
for (auto& task : tasks) {
RunParallel([=]() { DoWork(<args>); latch.count_down(); });
}
latch.await();
其中RunParallel可以使用std :: thread或您喜欢的任何其他机制来调度并行事件。
这种方法的优点是对象生命周期更简单。 ArgT对象完全适用于DoWork调用的范围。 DoWork的参数与包含它们的闭包完全一样长。这也使得向DoWork调用添加返回值(例如错误代码)变得更加容易:调用者只需从锁存器切换到线程安全队列,并在完成时读取结果。
这种方法的缺点是它需要实际的线程,而不仅仅是boost :: asio :: io_service。 (例如,DoWork()中的RunParallel调用无法阻止等待来自调用方的RunParallel调用返回。)因此,您必须将代码构造为严格分层的线程池,或者您必须允许潜在无限数量的线程。
答案 1 :(得分:0)
一个选项是此处的delete this
不代码气味。最多应该将它包装到一个小型库中,该库可以检测是否所有连续回调都被销毁而没有调用done_()
。