Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can an object erase itself from a standard C++ container? [duplicate]

The following code

#include <iostream>
#include <map>

struct foo
{
  void kill(std::map<int, foo>& m, int i)
  {
    m.erase(i);
  }
};

int main()
{
  std::map<int, foo> m;

  m.emplace(1, foo() );

  std::cout << m.size() << std::endl;

  m[1].kill(m, 1);

  std::cout << m.size() << std::endl;
}

compiles without warning (g++), executes without error and judging by the output the kill method erases the foo object from the map. However, I feel that this might actually be undefined behavior. It seems that in the kill method after the line m.erase(i) this no longer points to a valid object.

What does the C++ standard say about this?

like image 863
Bobby Avatar asked May 01 '16 21:05

Bobby


1 Answers

When you enter your kill, m[1] (from m[1].kill(m, 1);) statement has been fully evaluated as being the foo object you are calling kill on.

Then you do m.erase(i); ending up destroying the current object foo.

As far as you write absolutely no statement using the current object (this) before you return from the kill function, that's perfectly acceptable and safe (as commented by the posts referenced by Auriga and Barry). Even if the current object does not exist anymore, your function will return safely from the stack, no reason for it to fail as far as I know.

As an illustration, this would end up with undefined behaviour and must NOT BE DONE:

struct foo
{
  void kill(std::map<int, foo>& m, int i)
  {
    m.erase(i);
    cout << attribute; // don't do that! current foo object does not exist anymore
  }
  int attribute;
};

So let's say what you are doing is risky, but valid and safe if you do it well.

As an illustration, this would end up with defined behaviour and CAN BE DONE:

struct foo
{
  void kill(std::map<int, foo>& m, int i)
  {
    int theAttribute = attribute;
    m.erase(i);
    cout << theAttribute; // OK!
  }
  int attribute;
};

Having a method delete the current object is probably not a good practice anyway (specially if another developer modifies the code later...he could easily make it crash with the first example above). At least put an explicit comment in the code to tell the current object could have been destroyed (note that kill could destroy the current object, another one, or none...depending on m content and i):

struct foo
{
  void kill(std::map<int, foo>& m, int i)
  {
    m.erase(i);
    // careful! current object could have been destroyed by above statement and may not be valid anymore! Don't use it anymore!
  }
};
like image 173
jpo38 Avatar answered Oct 20 '22 16:10

jpo38