Почему в STL-карте структур оператор "[]" вызывает вызов dtor структуры еще 2 раза? - PullRequest
8 голосов
/ 25 октября 2010

Я создал простой тестовый пример, демонстрирующий странное поведение, которое я заметил в большой кодовой базе, над которой я работаю. Этот тест ниже. Я полагаюсь на оператор «[]» карты STL, чтобы создать указатель на структуру в карте таких структур. В приведенном ниже тесте строка ...

TestStruct *thisTestStruct = &testStructMap["test"];

... получает указатель (и создает новую запись на карте). Странная вещь, которую я заметил, заключается в том, что эта строка не только вызывает создание новой записи на карте (из-за оператора «[]»), но по какой-то причине вызывает деструктор структуры, вызываемый два дополнительных раза. Я явно что-то упускаю - любая помощь очень ценится! Спасибо!

#include <iostream>
#include <string>
#include <map>

using namespace std;
struct TestStruct;

int main (int argc, char * const argv[]) {

    map<string, TestStruct> testStructMap;

    std::cout << "Marker One\n";

    //why does this line cause "~TestStruct()" to be invoked twice?
    TestStruct *thisTestStruct = &testStructMap["test"];

    std::cout << "Marker Two\n";

    return 0;
}

struct TestStruct{
    TestStruct(){
        std::cout << "TestStruct Constructor!\n";
    }

    ~TestStruct(){
        std::cout << "TestStruct Destructor!\n";
    }
};

код выше выводит следующее ...

/*
Marker One
TestStruct Constructor!             //makes sense
TestStruct Destructor!               //<---why?
TestStruct Destructor!               //<---god why?
Marker Two
TestStruct Destructor!               //makes sense
*/

... но я не понимаю, что вызывает первые два вызова деструктора TestStruct? (Я думаю, что последний вызов деструктора имеет смысл, потому что testStructMap выходит из области видимости.)

Ответы [ 7 ]

18 голосов
/ 25 октября 2010

Функциональность std::map<>::operator[] эквивалентна

(*((std::map<>::insert(std::make_pair(x, T()))).first)).second

выражение, как указано в спецификации языка. Это, как вы можете видеть, включает в себя создание по умолчанию временного объекта типа T, копирование его в объект std::pair, который позднее (снова) копируется в новый элемент карты (при условии, что его там не было). уже). Очевидно, что это даст несколько промежуточных T объектов. Разрушение этих промежуточных объектов - это то, что вы наблюдаете в своем эксперименте. Вы пропускаете их конструкцию, так как не генерируете никакой обратной связи от копирующего конструктора вашего класса.

Точное количество промежуточных объектов может зависеть от возможностей оптимизации компилятора, поэтому результаты могут отличаться.

8 голосов
/ 25 октября 2010

У вас есть несколько невидимых копий:

#include <iostream>
#include <string>
#include <map>

using namespace std;
struct TestStruct;

int main (int argc, char * const argv[]) {

    map<string, TestStruct> testStructMap;

    std::cout << "Marker One\n";

    //why does this line cause "~TestStruct()" to be invoked twice?
    TestStruct *thisTestStruct = &testStructMap["test"];

    std::cout << "Marker Two\n";

    return 0;
}

struct TestStruct{
    TestStruct(){
        std::cout << "TestStruct Constructor!\n";
    }

    TestStruct( TestStruct const& other) {
        std::cout << "TestStruct copy Constructor!\n";
    }

    TestStruct& operator=( TestStruct const& rhs) {
        std::cout << "TestStruct copy assignment!\n";
    }

    ~TestStruct(){
        std::cout << "TestStruct Destructor!\n";
    }
};

Результат:

Marker One
TestStruct Constructor!
TestStruct copy Constructor!
TestStruct copy Constructor!
TestStruct Destructor!
TestStruct Destructor!
Marker Two
TestStruct Destructor!
5 голосов
/ 25 октября 2010

добавить следующее в интерфейс TestStruct:

TestStruct(const TestStruct& other) {
    std::cout << "TestStruct Copy Constructor!\n";
}   
4 голосов
/ 25 октября 2010

Ваши два таинственных вызова деструктора, вероятно, связаны с вызовами конструктора копирования, происходящими где-то в пределах std::map. Например, вполне возможно, что operator[] default-создаст временный TestStruct объект, а затем скопирует его в нужное место на карте. Причина, по которой существует два вызова деструктора (и, следовательно, возможно, два вызова конструктора копирования), зависит от реализации и будет зависеть от реализации компилятора и стандартной библиотеки.

4 голосов
/ 25 октября 2010

operator[] вставляется в map, если там еще нет элемента.

Что вам не хватает, так это вывод для предоставленного компилятором конструктора копирования в вашем TestStruct, который используется при ведении контейнера. Добавьте этот вывод, и все должно иметь больше смысла.

РЕДАКТИРОВАТЬ: Ответ Андрея побудил меня взглянуть на источник в Microsoft VC ++ 10's <map>, что вы также можете сделать, чтобы выполнить это во всех его подробностях. Вы можете увидеть insert() звонок, на который он ссылается.

mapped_type& operator[](const key_type& _Keyval)
    {   // find element matching _Keyval or insert with default mapped
    iterator _Where = this->lower_bound(_Keyval);
    if (_Where == this->end()
        || this->comp(_Keyval, this->_Key(_Where._Mynode())))
        _Where = this->insert(_Where,
            value_type(_Keyval, mapped_type()));
    return ((*_Where).second);
    }
0 голосов
/ 07 декабря 2016

Вы можете проверить это с помощью этого более простого кода.

#include <iostream>
#include <map>

using namespace std;

class AA
{
public:
  AA()            { cout << "default const" << endl; }
  AA(int a):x(a)  { cout << "user const" << endl; }
  AA(const AA& a) { cout << "default copy const" << endl; }
  ~AA()           { cout << "dest" << endl; }
private:
  int x;
};

int main ()
{
  AA o1(1);

  std::map<char,AA> mymap;

  mymap['x']=o1;    // (1)

  return 0;
}

Приведенный ниже результат показывает, что (1) приведенный выше строчный код выполняет (1 по умолчанию const) и (2 по умолчанию copy const) вызовы.

user const
default const        // here
default copy const   // here
default copy const   // here
dest
dest
dest
dest
0 голосов
/ 26 октября 2010

Итак, урок - не помещайте структуры на карту, если вы заботитесь об их жизненных циклах. Используйте указатели или, что еще лучше, shared_ptrs к ним

...