【问题标题】:Removing an object from a singleton-managed container upon destruction在销毁时从单例管理的容器中删除对象
【发布时间】:2021-03-06 19:06:20
【问题描述】:

我有一个Singleton 类,它管理Items 的容器,公开允许在容器中添加或删除Items 的公共函数。

class Item;
typedef std::shared_ptr<Item> ItemPtr;

class Singleton
{
public:
  static Singleton& Instance()
  {
    static std::unique_ptr<Singleton> Instance(new Singleton);
    return *Instance;
  }

  void Add(ItemPtr item)
  {
    mContainer.push_back(item);
  }

  void Remove(ItemPtr item)
  {
    for (auto it = mContainer.begin(); it != mContainer.end(); it++)
      if (*it == item)
        mContainer.erase(it);
  }

private:
  std::vector<ItemPtr> mContainer;
};

我希望Item 能够通过Add() 方法将自己添加到Singleton 容器中,并在容器销毁时将其从容器中移除。

class Item
{
public:
  Item() {}

  ~Item() 
  {
    Singleton::Instance().Remove(ItemPtr(this));
  }

  void Add()
  {
    Singleton::Instance().Add(ItemPtr(this));
  }
};

当我运行下面的示例时,我在Singleton::Remove() 上遇到了崩溃,特别是在mContainer.begin() 上出现了EXC_BAD_ACCESS

int main()
{
  Item* a = new Item();
  Item* b = new Item();

  a->Add();
  b->Add();

  delete a;
  delete b;
}

这似乎表明mContainer 不再存在。查看调用堆栈,我还可以看到根调用堆栈帧之一是析构函数Singleton::~Singleton(),这可以解释为什么mContainer 不再存在。

我尝试了一种不同的方法:我没有使用std::shared_ptr&lt;Item&gt;,而是简单地使用了原始指针(即Item*),并在代码中进行了适当的替换。它没有问题。

我的问题是:

  • 我猜发生的事情是Item对象的所有权只有在Singleton被销毁后才被shared_ptr释放,这会导致错误。这是正确的吗?
  • 如果Singleton中的容器是shared_ptr&lt;Item&gt;,是不是就不能做我想做的事了?
  • 如果没有,我该怎么办?

【问题讨论】:

  • 最重要的是,Singleton::Remove 表现出未定义的行为。 mContainer.erase(it) 使it 无效,然后it++ 访问这个现在无效的迭代器。
  • 您可能正在寻找std::enable_shared_from_thisItemPtr(this) 是个坏主意,因为它会导致双重破坏:ItemPtr 临时在分号处被破坏,并将在 this 上调用 delete
  • 原始指针 ab 隐藏在推入容器的 std::shared_ptr 实例中。那些赤裸裸的delete 电话是错误的。动态对象要么由智能指针管理,要么不是。你不能两全其美,而且你当然不能让他们的最终毁灭两次。你绝对应该查看enabled_shared_from_this,并记住,如果你走这条路,任何产生std::shared_ptr的实例请求必须从一开始就从一开始就进行管理。跨度>
  • 由于对象在单例中总是存在的,如何销毁该对象?这有点循环。

标签: c++ singleton shared-ptr


【解决方案1】:

尽管首先这样做很明智,但如果您愿意使用并遵守std::enabled_shared_from_this 的限制,就可以实现您想要的。见下文:

#include <iostream>
#include <algorithm>
#include <memory>
#include <vector>

struct Item;
typedef std::shared_ptr<Item> ItemPtr;

class Singleton
{
private:
    Singleton() {}

public:
    static Singleton &Instance()
    {
        static Singleton s;
        return s;
    }

    void Add(ItemPtr item)
    {
        mContainer.emplace_back(std::move(item));
    }

    void Remove(const ItemPtr& item)
    {
        mContainer.erase(
            std::remove(mContainer.begin(), mContainer.end(), item), 
            mContainer.end());
    }

    void Clear()
    {
        mContainer.clear();
    }

private:
    std::vector<ItemPtr> mContainer;
};

// note derivation. this means you can get a std::shared_ptr<Item>
// via `shared_from_this` , but it also means the object itself
// MUST be an actual shared object to begin with.
struct Item : public std::enable_shared_from_this<Item>
{
    void Add()
    {
        Singleton::Instance().Add(shared_from_this());
    }
};


int main()
{
    ItemPtr a = std::make_shared<Item>();
    ItemPtr b = std::make_shared<Item>();

    // add to the singleton container
    a->Add();
    b->Add();

    // report reference count of 'a'
    std::cout << "before removal 'a' has " << a.use_count() << " references\n";
    Singleton::Instance().Remove(a);
    std::cout << "after removal 'a' has " << a.use_count() << " references\n";
}

输出

before removal 'a' has 2 references
after removal 'a' has 1 references

其中最重要的部分是在main 中创建ab。请注意,它们实际上是由 std::shared_ptr 从一开始就被管理的。这是std::enable_shared_from_this 正常工作所必需的。其余的相当简单。通过基类std::enable_shared_from_this 提供的shared_from_this() 成员可以从Item 的任何成员的内部 中获取引用碰撞std::shared_ptr

简而言之,采用这种方法对您有用,但在任何时候都不能使用shared_from_this(),除非它被触发的对象首先已经由std::shared_ptr 管理。请记住这一点。

【讨论】:

    猜你喜欢
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    • 2015-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-22
    • 1970-01-01
    相关资源
    最近更新 更多