6

I have std::map<const char*, std::shared_ptr<Resource, ResourceDeleter>>, which acts like a ResourceDatabase. The item into std::map is inserted when someone requests Resource that isn't already in the std::map (otherwise it will be served from the std::map). Similarly, when the Resource is not used anymore, it should be removed. In ResourceDeleter, i will remove the record from the std::map. The problem is that ResourceDeleter is never called, because there is still one std::shared_ptr in the std::map. When the item is inserted into std::map, there will be always 2 std::shared_ptr - one for the code that needs the Resource and one in the std::map. When the first std::shared_ptr is deleted, i need to remove Resource from the std::map.

Krab
  • 6,526
  • 6
  • 41
  • 78
  • 1
    You should look into std::weak_ptr http://stackoverflow.com/q/12030650/1922748 – Martin Schlott Mar 29 '15 at 16:32
  • When you have a parent - child relation, never store the parent pointer in the child as a shared pointer (If you do, the dependency becomes circular). Have only one shared pointer to the child in parent. (You might use a raw or weak pointer in the child) –  Mar 29 '15 at 16:41
  • 1
    Maybe OT but be very careful with `const char *` as key: pointer comparison will be used for the keys, which means that `the_map["abc"]` at one point might be a different entry to `the_map["abc"]` at another point – M.M Mar 30 '15 at 00:00

2 Answers2

2

You should use a std::weak_ptr in your std::map.

#include <iostream>
#include <memory>

struct A {
  std::string hello() { return "hello"; }
  ~A() { std::cout << "Destructor ~A() called" << std::endl; }
};

void use(std::weak_ptr<A> const& wp)
{
  if (auto spt = wp.lock())
  {
    std::cout << "Accessing object... " << spt->hello() << "\n";
  }
  else
  {
    std::cout << "use(): object was deleted\n";
  }
}

int main()
{
  std::weak_ptr<A> wp;
  {
    std::shared_ptr<A> sp = std::make_shared<A>();
    wp = sp;
    use(wp);
    std::cout << "Destructing sp" << std::endl;
  }
  use(wp);
  std::cout << "Destructing wp" << std::endl;
}

Output:

Accessing object... hello
Destructing sp
Destructor ~A() called
use(): object was deleted
Destructing wp

Carlo Wood
  • 5,648
  • 2
  • 35
  • 47
  • This also solves the following problem: what if you also want, eg. a `std::map>`. Now you can't rely on the reference count to figure out if the object should be deleted. – roeland Mar 30 '15 at 00:57
1

As I understand the problem, the goal is that when the last shared pointer to a Resource is deleted, the Resource should be removed from the ResourceMap and then deleted. As you say, that can't work if a shared pointer to the Resource is kept in the ResourceMap, because the shared pointer in the map will always be the last shared pointer to the Resource.

So there are two simple solutions. One is to keep in the Map a weak pointer (std::weak_ptr) to the Resource. The other, possibly simpler solution, is to keep the Resource itself in the ResourceMap. In both cases, you need to wrap the std::map (or std::unordered_map) because you need to return a shared pointer to the Resource, which is not the mapped_type of the associative container.

The Resource can be constructed directly into the map using map::emplace.

Actually implementing ResourceDeleter is not trivial, since in order to do the deletion, you need to have access to both the map and the key (or an iterator to the value). A pointer to the value (which is what is in the shared_ptr) is clearly not sufficient.

To cut down on the size of the shared_ptr, I'd consider making the object of the shared_pointer an iterator or a pointer to the std::pair<const key_type, mapped_type>. Then you would need a wrapper around the smart pointer so that the * operator would end up being implemented as p->second rather than *p.

rici
  • 234,347
  • 28
  • 237
  • 341
  • What happens if the weak_ptr in the map is accessed after the shared_ptr has been freed? – M.M Mar 30 '15 at 00:03
  • @MattMcNabb: If the resource deleter removes the entry from the map, that's not possible. But in general, a weak_ptr to an object whose last shared_ptr has been deleted is effectively a NULL pointer. – rici Mar 30 '15 at 00:04