Почему теряется указатель на объект?
При создании динамического объекта в методе, я хочу сохранить указатель в мапу (на случай повторного использования) и вернуть его. Возвращается он всегда правильно, но в случае использования emplace, в мапе он почему-то теряется и в следующем обращении, создается заново. А при записи по ключу все отлично работает. (код прилагаю)
Почему так происходит? Где теряется указатель?
#include <map>
#include <iostream>
using namespace std;
class CTest
{
public:
CTest()
:m_map()
{}
~CTest()
{
for (auto& item : m_map)
{
delete item.second;
}
}
int* GetValue(int i)
{
int* result = m_map[i];
if (result == nullptr)
{
result = new int(i);
m_map[i] = result; // works good
//m_map.emplace(i, result); // doesn't work
}
return result;
}
private:
map<int, int*> m_map;
};
int main()
{
CTest c;
cout << *c.GetValue(2) << endl;
cout << *c.GetValue(3) << endl;
cout << *c.GetValue(2) << endl;
return 0;
}
Ответы (2 шт):
В приведенном коде вставка в контейнер происходит на строке int* result = m_map[i]; так как оператор [] всегда будет вставлять в контейнер новый элемент, если его еще нет. Соответственно последующий emplace добавлять ничего не будет. Вместо этого следует сначала иcпользовать find
auto p_item{m_map.find(key)};
if (m_map.end() == p_item)
{
p_item = m_map.emplace(::std::piecewise_construct, ::std::forward_as_tuple(key), ::std::forward_as_tuple(value)).first;
}
return p_item->second;
Также в контейнере не следует хранить сырые указатели.
Между operator[] и insert/emplace есть одна важная разница. operator[], если элемент уже есть, заменяет его. А вот insert/emplace ничего не делают. Когда в самом начале функции Вы вызываете [], элемент уже создан...
UPD
мой, понятный вариант функции GetValue
int* GetValue(int i)
{
auto it = m_map.find(i); // поищем его
if (it == m_map.end()) // если его нет
{
// создадим. emplace возвращает нам пару, в которой первый элемент - нужный нам итератор.
it = m_map.emplace(i, new int(i)).first;
}
// этот итератор содержит либо существующий элемент, либо созданный.
return it->second;
}
emplace вторым элементом пары возвращает признак успешности вставки. Можно было конечно сделать на базе этого (втиснув emplace прям в if), но тогда что делать с созданным элементом? его нужно удалять (ответ user7860670 этого как раз это не учитывает. Да, если использовать умные указатели, память почистится, но лишнее создание-удаление объекта (который может быть ну очень тяжелым на создание) может свести такую хитрую конструкцию в ответе (которая пытается выглядеть супер оптимизированной) в ноль).
Но если уж заморачиваться на скорость, то можно перейти на emplace_hint. Оно первым параметром получает указатель на "предполагаемое место вставки". И если с ним угадать, то вставка может быть ну очень быстрой (по документации - амортизированная константная).
int* GetValue(int i)
{
auto it = m_map.lower_bound(i);
if (it == m_map.end() || it->first != i)
{
it = m_map.emplace_hint(it, i, new int(i));
}
return it->second;
}
Конечно, нужно смотреть, как именно создается объект, который вставляется в мапу, может это все перевернет.