【问题标题】:How to keep alive a shared_ptr-object of which members are used in an asynchronous function?如何使异步函数中使用其成员的 shared_ptr 对象保持活动状态?
【发布时间】:2017-04-06 06:07:35
【问题描述】:

想象一下下面的代码:

void async(connection *, std::function<void(void)>);

void work()
{
  auto o = std::make_shared<O>();
  async(&o->member, [] { do_something_else(); } );
}

async 例如,将使用作为指针传递的o 中的member 启动一个线程。但是当oasync() 被调用之后立即超出范围并且它将被删除并且成员也将被删除时,这样写。

如何正确而巧妙地解决这个问题(!)?

显然one solution 是将o 传递给捕获列表。即使不使用,也保证不会优化捕获。

  async(&o->member, [o] { do_something_else(); } );

但是,最近的编译器 (clang-5.0) 将 -Wunused-lambda-capture 包含在 -Wextra 集合中。这种情况会产生未使用的lambda捕获警告。

我在 lamdba 中添加了 (void) o; 以消除此警告。

  async(&o->member, [o] { 
      (void) o; 
      do_something_else(); 
    });

有没有更优雅的方法来解决这个范围问题?

(这个问题的根源来源于using write_async of boost::asio

【问题讨论】:

  • member 是采用std::function&lt;void ()&gt; 的静态函数吗?这是我能想到的唯一可能性,在这种情况下o 甚至不需要活着。
  • 为了简单起见,假设memberint
  • 那么int* 是如何成为可调用函数的呢?
  • async 是使用 int* 作为参数调用的函数。我将扩展我的代码示例。
  • 哦,所以async 不是std::async

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


【解决方案1】:

Boost.Asio 似乎使用suggest 使用enable_shared_from_this 来保持拥有“connection”的任何东西活着,而有待处理的操作使用它。例如:

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 的所有逻辑。

【讨论】:

  • 这是使用 asio 的 方式。您可以使用这种方法使套接字/客户端处理程序等保持活动状态,并允许它们在不再有异步事件要为它们处理时优雅地过期。
【解决方案2】:

我建议您的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&lt;connection&gt;shared_ptr&lt;connection&gt;

void async(std::shared_ptr<connection>, std::function<void ()> f);

这比在闭包中捕获connection 的所有者要好,这可能会产生无法预料的副作用(包括async 可能期望connection 在函数对象被调用和销毁后保持活动状态) .

【讨论】:

  • 同意,但我无法更改我在现实中使用的异步函数的原型。它是 boost::asio 之一(请参阅我问题末尾的链接)。
【解决方案3】:

不是一个很好的答案,但是...

似乎不一定有“更好”/“更清洁”的解决方案,尽管我建议更“自我描述”的解决方案可能是为线程操作创建一个函子,它显式绑定成员函数和其中的 shared_ptr 实例。使用虚拟 lambda 捕获不一定能捕获意图,并且有人可能会稍后出现并将其“优化”到一个糟糕的结果。不过,不可否认,将函子与 shared_ptr 绑定的语法要复杂一些。

无论如何,我的 2c(我已经按照我的建议做了类似的事情,供参考)。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-13
      • 2013-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-03
      • 1970-01-01
      • 2019-02-14
      相关资源
      最近更新 更多