Meysam
Meysam

Reputation: 18137

Removing all the entries pointing to a deleted object from a map

So here is the code I am dealing with:

class A
{
public:
    A(){}
    virtual ~A(){}
    void Log(){printf("Log A\n");}
};

int main(int argc, char**argv) 
{
    A* a = new A();

    a->Log(); // "Log A"

    map<int,A*> m;
    m[1] = a;
    m[2] = a;
    m[3] = a;

    m[1]->Log(); // "Log A"

    delete a;
    a = NULL;

    m[1]->Log(); // "Log A"

    return 0;
}

Output:

Log A
Log A
Log A

My questions:

  1. Is it only by chance that calling m[1]->Log() does not throw exception after delete a?
  2. What's the best approach to erase all the entries in the map pointing to the deleted instance of A? I mean I want all m.find(1), m.find(2) and m.find(3) to return m.end() after deleting a. Any advice would be appreciated.

Upvotes: 2

Views: 165

Answers (4)

Luchian Grigore
Luchian Grigore

Reputation: 258608

  1. Yes and no. Technically it's undefined behavior, but usually (don't rely on this) calling non-virtual methods that don't access members appears to work on most compilers because most compilers implement this call without dereferencing this (which is the invalid part). So, in the standard's opinion, it's by chance. For most compilers, it's intended (or at least a side-effect of how function calls are handled).

  2. Use smart pointers instead. To remove the elements, you can iterate through the map and compare each value to yours. When you reach one, use erase. Iterators are invalidated after erase.

Upvotes: 5

juanchopanza
juanchopanza

Reputation: 227418

  1. Anything that happens when you dereference a deleted object is undefined behaviour, so even getting an exception could be considered to be "by chance"

  2. The best approach is to couple the deletion of the object that is pointed to with the lifetime of something you have a handle on. So in your case, you could decide that it is best if the object is deleted if the pointer is removed from the map. To achieve this, you could use a map of int to std::unique_ptr<A> instead of one of raw pointers.

Edit: After looking at the requirements in more detail:

Now, since you want to remove elements whose mapped type points to a deleted object, and there is no way to determine whether the memory a pointer points to has been deleted, I see no simple way of removing these entries from the map other than doing it all in one function call. And since std::map et al do not like std::remove_if, one can use a loop:

template <typename T1, typename T2>
void removeEntriesAndDelete(std::map<T1, T2*>& m, T2*& item) {
  for (auto i = m.begin(); i != m.end(); ) {
    if ( item == i->second) {
      m.erase(i++);
    } else {
      ++i;
    }
  }
  delete item;
  item=0;
} 

int main() {

  A* a = new A;
  std::map<int,A*> m;
  m[1] = a;
  m[2] = a;
  m[3] = a;
  std::cout << std::boolalpha;
  std::cout << a << ", " << bool(m[1]) << ", " << bool(m[2]) << ", " << bool(m[3]) <<"\n";

  removeEntriesandDelete(m, a);
 std::cout << a << ", " << bool(m[1]) << ", " << bool(m[2]) << ", " << bool(m[3]) <<"\n";
}

Upvotes: 4

James Kanze
James Kanze

Reputation: 153919

Generally, objects which enrolled somewhere must notify the object where they are enrolled. In simple cases like your example code, it's relatively simple to go through the map, erasing each element which points to your object, but I assume that your actual use case is less trivial. The usual solution (in fact, the only one which really works in practice) involves the observer pattern; when an object saves a pointer to your object, either directly or in a map or a list or whatever, it also enrols with your object, requesting notification when your object is destructed. Your object keeps a list of these observers, and will notify them in its destructor. Applied to a simple case like yours, it looks like a lot of unnecessary code, but in the context of actual applications where this pattern occurs, it's not that much.

Upvotes: 2

Brady
Brady

Reputation: 10357

It works by luck as you mention.

Either use something like smart pointers as previously mentioned, or encapsulate the map and value handling in a class, where you could have a method that removes the object from the map and deletes the object.

Upvotes: 1

Related Questions