具有独立的shared_ptrs指向同一对象是否合法?

时间:2019-02-13 13:47:29

标签: c++ shared-ptr

关于shared_ptr的许多文章都警告不要意外地为同一对象创建独立的shared_ptr。 例如,this article。它有评论// Bad, each shared_ptr thinks it's the only owner of the object

但是,如果那正是我想要的行为怎么办?例如:

auto* object = new Object();
auto ptr1 = std::shared_ptr<Object>(object);
auto ptr2 = std::shared_ptr<Object>(object, [ptr1](Object* obj){ obj->cleanup(); });
ptr2 = nullptr;
ptr1 = nullptr;

这在GCC 6.3上可以完美运行,但是这样做合法吗?(例如,标准允许使用吗?

3 个答案:

答案 0 :(得分:1)

具有两个拥有相同对象的shared_ptr对象有时会起作用。 Object源自std::enable_shared_from_this<Object>的地方不起作用。在这种情况下,分配给shared_ptr的过程中的魔力将导致您不确定的行为。

  

std :: shared_ptr的构造函数检测到明确且可访问的(自C ++ 17起)enable_shared_from_this基的存在,并将新创建的std :: shared_ptr分配给内部存储的弱引用(如果尚未由实时std拥有) :: shared_ptr(自C ++ 17起)。为已经由另一个std :: shared_ptr管理的对象构造一个std :: shared_ptr不会参考内部存储的弱引用,从而导致不确定的行为。

https://en.cppreference.com/w/cpp/memory/enable_shared_from_this

  

我不希望用户从外部线程中同时删除那些对象,所以我想使用仅计划删除的自定义删除器。

解决方案将取决于清理操作是否需要共享计数(即,是否需要花费超过一滴答的时间)。

简单情况:

auto deleter = [&scheduler](Object* p)
{
    auto delete_impl = [p]()
    {
        p->cleanup();
        delete p;
    };
    scheduler.post(delete_impl);
};

auto po = std::shared_ptr<Object>(new Object(), deleter);

不太简单的情况:

在清理过程可能要花费一个“ tick”以上的时间的情况下,我从cppreference的文档中不清楚是否将p重新分配给另一个shared_ptr<Object>进行清理是否有效相。即使严格来说,它也是一个黑暗的角落,我不相信在所有库实现中都将行为标准化。

为了安全起见,让我们定义一个新对象,使其在清理过程中充当共享句柄:

struct DyingObjectHandle : std::enable_shared_from_this<DyingObjectHandle>
{
  DyingObjectHandle(Object* p) : p(p) {}

  void cleanup()
  {
    auto self = shared_from_this();
    ... etc
  }

  void final_destroy()
  {
    delete p;
  }

  Object *p;
};

然后修改删除器:

auto deleter = [&scheduler](Object* p)
{
    auto doh = std::make_shared<DyingObjectHandle>(p);
    scheduler.post([doh = std::move(doh)]()
    {
        doh->cleanup();
    });
};

auto po = std::shared_ptr<Object>(new Object(), deleter);

最后:

  

实际上,库是boost :: asio的包装器

这通常是导致效率低下的常见原因。

通常应将asio::io_context视为整个应用程序的单例对象。它表示“应用程序范围的IO调度循环”。当N个线程运行相同的io_context,每个启用了io的对象都有自己的strand且所有处理程序都通过这些链调度时,可以实现最大的并发性,例如:

timer_.async_wait(asio::bind_executor(my_strand_, 
                  [self = shared_from_this()](error_code ec)
{
   // ...handle the timer.
}); 

这样,完成线程处理程序就无关紧要了。如果在同一个io对象上发生多个并发操作,则与通过同一个互斥对象竞争或绑定到特定线程的io_context相比,它们将通过链更有效地进行序列化。 / p>

答案 1 :(得分:0)

这是合法的。唯一不合法的事情是双重删除指向的对象。您可以通过让一个shared_ptr使用自定义删除器来防止这种情况。

这是好习惯吗?它会通过代码审查吗?它会引起眉毛吗?您自己决定。

我会努力不使用这样的结构。

答案 2 :(得分:0)

您显示的内容似乎是合法的。

  

我不希望用户从外部线程中同时删除那些对象,所以我想使用仅计划删除的自定义删除器。

我建议使用另一种方法:只需将一个共享指针级别与一个自定义删除器一起使用。在删除器中,将指针添加到线程安全队列中,以在正确的线程中销毁该队列。一种简单的方法是将唯一的指针存储在队列中,然后只需清除队列以释放内存即可。