如何创建智能指针映射,当指针超出范围时删除其元素
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
。但我正在为如何将项目插入地图而苦苦挣扎。这就是我现在正在尝试的。
(在这个例子中我只是使用 int
和 string
来保持简单——我稍后会正确地模板化它)。
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;
};
我想要一个带有指向自定义类型的智能指针的地图容器。当你把钥匙放进去时,你会得到对象的 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
。但我正在为如何将项目插入地图而苦苦挣扎。这就是我现在正在尝试的。
(在这个例子中我只是使用 int
和 string
来保持简单——我稍后会正确地模板化它)。
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;
};