Memory management C++;回调链中使用的对象的内存管理模式

Memory management C++;回调链中使用的对象的内存管理模式,memory-management,c++11,Memory Management,C++11,我使用的几个代码库包括以下模式中手动调用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) :

我使用的几个代码库包括以下模式中手动调用
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_;
};
类工作者{
公众:
void DoWork(ArgT arg,std::函数完成){
新工作进程(std::move(arg),std::move(done)).Start();
}
私人:
辅助程序(ArgT arg,std::函数完成)
:arg(std::move(arg)),
完成(标准::移动(完成)),
闩锁(2){}//容易出错的闩锁接口不是这个问题的重点。:)
void Start(){
Async1(,[=](){this->Method1();});
}
void Method1(){
开始并行(,[=](){this->latch_u.count_udown();});
开始并行(,[=](){this->latch_u.count_udown();});
然后([=](){this->Finish();});
}
空隙饰面(){
完成了;
//注意手动内存管理!
删除此项;
}
ArgT arg_
std::函数完成;
闩锁;
};

现在,在现代C++中,显式<代码>删除<代码>是代码的气味,在某种程度上是<代码>删除这个< /代码>。然而,我认为这种模式(创建一个对象来表示回调链管理的一块工作)从根本上说是一个好主意,或者至少不是一个坏主意

所以我的问题是,我应该如何重写这个模式的实例来封装内存管理


我认为一个不好的选择是将
工作者
存储在
共享的\u ptr
中:从根本上说,这里不共享所有权,因此不需要参考计数的开销。此外,为了在回调中保持
共享的ptr
的副本处于活动状态,我需要从
中继承,从这个
中启用共享的ptr,并记住在lambdas之外调用它,并将
共享的ptr
捕获到回调中。如果我曾经直接使用
this
编写简单代码,或者在回调lambda中调用
shared\u from\u this()
,则可以提前删除对象。

一个选项是
删除此
这里没有代码气味。最多,它应该被包装到一个小的库中,该库可以检测是否在不调用
done()。但我认为这是延续传递风格的自然组成部分,对我来说,这本身就是一种代码味道

根本问题在于,此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();
  }
};
类工作者{
公众:
无效定位销(ArgT arg){
//Async1是一个错误;请稍后修复。现在,请显式同步。
闩锁异步_完成(1);
Async1(,[&](){async_done.count_down();});
异步_完成。等待();
闩锁平行完成(2);
RunParallel([&](){DoStuff();parallel_done.count_down();});
RunParallel([&](){DoStuff();parallel_done.count_down();};
平行完成。等待();
}
};
在调用者端,它可能看起来像这样:

Latch latch(tasks.size());
for (auto& task : tasks) {
  RunParallel([=]() { DoWork(<args>); latch.count_down(); });
}
latch.await();
闩锁(tasks.size());
用于(自动任务:任务(&T){
RunParallel([=](){DoWork();lock.count_down();});
}
satch.wait();
其中RunParallel可以使用std::thread或任何其他机制来调度并行事件

这种方法的优点是对象的生存期要简单得多。ArgT对象的生存期恰好与DoWork调用的作用域相同。DoWork的参数与包含它们的闭包的生存期一样长。这也使得添加返回值(如错误代码)变得更容易DoWork调用:调用方只需从闩锁切换到线程安全队列,并在结果完成时读取结果


这种方法的缺点是它需要实际的线程,而不仅仅是boost::asio::io_服务(例如,DoWork()中的RunParallel调用无法阻止来自调用方的RunParallel调用返回)因此,您要么必须将代码组织到严格的分层线程池中,要么必须允许无限数量的线程。

在我看来,有四个线程:第一个线程调用
Start
,为
Method1()
(并返回)创建一个线程a.Thread A启动两个线程并等待完成。能否通过提供参数A(临时)通过
std::Thread
创建线程A<代码> Works<代码> >代码> STD::线程 S Cor将将其参数复制/移动到线程本地存储,这样您就不必担心动态内存分配。只需要一个线程。请考虑使用Boosi::aso::IOIOService来插入回调。