Может ли объект стираться из стандартного контейнера С++?

Следующий код

#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;
}

компилируется без предупреждения (g++), выполняется без ошибок и, судя по результату, метод kill удаляет объект foo с карты. Однако я считаю, что это может быть поведение undefined. Похоже, что в методе kill после того, как строка m.erase(i) this больше не указывает на действительный объект.

Что говорит об этом стандарт С++?

Ответ 1

Когда вы вводите оператор kill, m[1] (from m[1].kill(m, 1);), он был полностью оценен как объект foo, который вы вызываете kill on.

Затем вы выполняете m.erase(i); в результате уничтожения текущего объекта foo.

Насколько вы пишете абсолютно никакой оператор, использующий текущий объект (this), прежде чем вы вернетесь из функции kill, это совершенно приемлемо и безопасно (как прокомментировали сообщения, на которые ссылается Auriga и Barry). Даже если текущий объект больше не существует, ваша функция будет безопасно возвращаться из стека, нет причин для его отказа, насколько мне известно.

В качестве иллюстрации это приведет к поведению undefined и НЕ должно быть выполнено:

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;
};

Итак, скажите, что вы делаете, это рискованно, но действительно и безопасно, если вы это сделаете хорошо.

В качестве иллюстрации это приведет к определенному поведению и 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;
};

Наличие метода удаления текущего объекта в любом случае, вероятно, не является хорошей практикой (особенно, если другой разработчик позже модифицирует код... он может легко заставить его сбой с первого примера выше). По крайней мере, поместите явный комментарий в код, чтобы сообщить, что текущий объект мог быть уничтожен (обратите внимание, что kill может уничтожить текущий объект, другой или нет... в зависимости от содержимого m и 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!
  }
};

Ответ 2

Это совсем не безопасно. m.erase(i) не будет пытаться стереть один и тот же объект, если он вызван несколько раз, но m[1].kill(m, 1) - это поведение undefined, если вызывается более одного раза. Чтобы быть немного более безопасным, m.at(1).kill(m, 1) выдаст ошибку out_of_range.