如何创建智能指针映射,当指针超出范围时删除其元素

How to create a map of smart pointers that deletes its elements when the pointers go out of scope

我想要一个带有指向自定义类型的智能指针的地图容器。当你把钥匙放进去时,你会得到对象的 shared_ptrs,但是当所有这些 shared_ptrs 都超出范围时,对象就会在贴图内部被销毁(与法线贴图相反,法线贴图对象一直存在,直到映射超出范围或直到它被显式删除)。

这是我想要实现的目标:

SpecialMap<int, std::string> map;

{
    std::shared_ptr<std::string> item = map.getElement(3);

}                 // element at 3 is deleted *inside the map* here, because the pointer goes out of scope.

assert(map.getSize() == 0);

我认为这可以通过为映射提供 std::weak_ptr 成员类型来完成,这不会增加引用计数,然后使用带有自定义删除器的 std::shared_ptr 来删除来自地图的元素,而不是调用 delete。但我正在为如何将项目插入地图而苦苦挣扎。这就是我现在正在尝试的。

(在这个例子中我只是使用 intstring 来保持简单——我稍后会正确地模板化它)。

class SpecialMap
{

public:

    std::shared_ptr<std::string> getElement(int i)
    {
        auto result = map.try_emplace(i, std::shared_ptr<std::string>(new std::string("text"),

            [this](std::string* s)  // here is the custom deleter
        {
            for (auto it = map.begin(); it != map.end();)
            {
                if (&*it->second.lock() == s)
                {
                    map.erase(it);
                    return;
                }
                else ++it;
            }
        }));

        return result.first->second.lock();
    }

    int getSize() const
    { return map.size(); }

private:

    std::unordered_map<int, std::weak_ptr<std::string>> map;
};

此代码无效,因为 try_emplace 中的 shared_ptr 立即超出范围,在项目返回之前将其删除。

任何人都可以在这里提出与 unordered_map 互动的更好方法吗?或者有更好的方法来解决这个问题吗?

首先,您的代码中存在内存泄漏,因为您自定义的接收指针的delter对象中没有删除,所以我在您的代码中添加了删除运算符。

据我了解,您应该先将指针存储到变量中,然后尝试将其放置到地图中,因此它应该如下所示

class SpecialMap
{
public:

    std::shared_ptr<std::string> getElement(int i)
    {
        auto ptr = std::shared_ptr<std::string>(new std::string("Hello"),

            [this](std::string* s)  // here is the custom deleter
        {
            std::cout << "custom deleter is called\n";
            for (auto it = map.begin(); it != map.end();)
            {
                if (&*it->second.lock() == s)
                {
                    map.erase(it);
                    return;
                }
                else ++it;
            }
            delete s;
        });
        auto result = map.try_emplace(i, ptr);

        return result.first->second.lock();
    }

    int getSize() const
    { return map.size(); }

private:

    std::unordered_map<int, std::weak_ptr<std::string>> map;
};

int main()
{
    SpecialMap map;

    {
        std::shared_ptr<std::string> item = map.getElement(3);
        std::cout << "some work with your ptr\n";
    }                 // element at 3 is deleted *inside the map* here, because the pointer goes out of scope.

}

输出应该是这样的

some work with your ptr
custom deleter is called

这是一些 live 自定义示例 class

考虑删除器的以下部分...

for (auto it = map.begin(); it != map.end();) {
    if (&*it->second.lock() == s) {
        map.erase(it);
        return;
    }
    else
         ++it;
}

但是删除器被调用只是因为引用计数已变为零。既然如此调用...

it->second.lock()

将return一个指向空指针的共享指针。因此,相等永远不会成立,并且 unordered_map 中的相应元素永远不会被删除。

处理此类事情的通常方法是使用类似...

using container_type = std::unordered_map<int, std::weak_ptr<foo>>;
container_type                                  m_map;
std::map<foo *, container_type::const_iterator> m_iters;

这里m_map真正的数据存储,当共享指针引用计数变为零时,m_iters可以用来找到合适的迭代器来擦除并调用删除器。

class special_map {
  using container_type = std::unordered_map<int, std::weak_ptr<std::string>>;
public:
  using const_iterator = container_type::const_iterator;

  std::shared_ptr<std::string> get_element (int i)
    {

      /*
       * Check to see if the requested element already exists.  If so simply
       * return it.
       */
      auto iter = m_map.find(i);
      if (iter != m_map.end())
        return iter->second.lock();
      std::shared_ptr<std::string> value(
        new std::string,
        [this](std::string *s)
          {
            std::cout << "deleter called on " << s << "\n";
            if (auto i = m_iters.find(s); i != m_iters.end()) {
              m_map.erase(i->second);
              m_iters.erase(i);
            }
            delete s;
          });
      iter = m_map.emplace(i, value).first;
      auto sp = iter->second.lock();
      m_iters[sp.get()] = iter;
      return sp;
    }
  int get_size() const
    {
      return m_map.size();
    }
  const_iterator begin () const
    {
      return m_map.begin();
    }
  const_iterator end () const
    {
      return m_map.end();
    }
private:
  container_type                                          m_map;
  std::map<std::string *, container_type::const_iterator> m_iters;
};

std::ostream &operator<< (std::ostream &os, const special_map &v)
{
  std::cout << "special_map@" << &v << "(" << v.get_size() << " elements)...\n";
  for (const auto &i: v) {
    auto sp = i.second.lock();
    std::cout << "[" << i.first << "] --> [std::string@" << sp.get() << "/" << (sp.use_count() - 1) << "]\n";
  }
  return os << "\n";
}

int main ()
{
  {
    special_map sm;
    {
      auto s1 = sm.get_element(5);
      std::cout << sm;
      {
        auto s2 = sm.get_element(5);
        std::cout << sm;
      }
      std::cout << sm;
    }
    std::cout << sm;
  }
}

示例输出是...

special_map@0x7ffedf44d6a0(1 elements)...
[5] --> [std::string@0xf75bc0/1]

special_map@0x7ffedf44d6a0(1 elements)...
[5] --> [std::string@0xf75bc0/2]

special_map@0x7ffedf44d6a0(1 elements)...
[5] --> [std::string@0xf75bc0/1]

deleter called on 0xf75bc0
special_map@0x7ffedf44d6a0(0 elements)...

编辑 1

如果在当前情况下使用的密钥类型足够 'lightweight' 与 int 一样,则可以避免使用额外的 m_iters 容器deleter lambda 按值捕获键并基于它执行映射查找。在这种情况下,代码变为...

class special_map {
  using container_type = std::unordered_map<int, std::weak_ptr<std::string>>;
public:
  using const_iterator = container_type::const_iterator;

  std::shared_ptr<std::string> get_element (int i)
    {

      /*
       * Check to see if the requested element already exists.  If so simply
       * return it.
       */
      auto iter = m_map.find(i);
      if (iter != m_map.end())
        return iter->second.lock();
      std::shared_ptr<std::string> value(
        new std::string,
        [this, i](std::string *s)
          {
            std::cout << "deleter called on " << s << "\n";
            m_map.erase(i);
            delete s;
          }
        );
      return m_map.emplace(i, value).first->second.lock();
    }
  int get_size() const
    {
      return m_map.size();
    }
  const_iterator begin () const
    {
      return m_map.begin();
    }
  const_iterator end () const
    {
      return m_map.end();
    }
private:
  container_type m_map;
};