如何保持在异步函数中使用哪些成员的shared_ptr-object?

时间:2017-04-06 06:07:36

标签: c++ c++11 asynchronous lambda

想象一下以下代码:

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

4 个答案:

答案 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,特别是成员方法leakreset 清理后,它看起来像以下最小的例子:

#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 classwalk成员函数)检索生命资源,仍然可以映射到底层库提供的内容,并且可以随时关闭它们,因此泄漏完全避免。
在你的情况下,你必须找到以某种方式避免问题的方法,这可能是一个问题,但它主要取决于实际的代码,我不能说。
一个可能的缺点是,在这种情况下,你被迫通过一个共享指针在动态存储上创建你的对象,否则整个事情就会爆发而不起作用。