Em um mapa STL de estruturas, por que o operador [] faz com que o dtor da estrutura seja invocado 2 vezes?

StackOverflow https://stackoverflow.com/questions/4017892

Pergunta

Criei um caso de teste simples exibindo um comportamento estranho que notei em uma base de código maior em que estou trabalhando. Este caso de teste está abaixo. Estou confiando no operador [] [] para criar um ponteiro para uma estrutura em um mapa de tais estruturas. No caso de teste abaixo, a linha ...

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

... me pega o ponteiro (e cria uma nova entrada no mapa). A coisa estranha que notei é que essa linha não apenas faz com que uma nova entrada no mapa seja criada (por causa do operador [] []), mas, por algum motivo, faz com que o destruidor da estrutura seja chamado de dois tempos extras. Obviamente, estou perdendo alguma coisa - qualquer ajuda é muito apreciada! Obrigado!

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

O código acima gera o seguinte ...

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

... Mas não entendo o que causa as duas primeiras invocações do destruidor de Teststruct? (Acho que a última invocação de destruidores faz sentido porque o teststructmap está saindo do escopo.)

Foi útil?

Solução

A funcionalidade de std::map<>::operator[] é equivalente a

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

expressão, conforme especificado na especificação do idioma. Isso, como você pode ver, envolve a construção de um objeto temporário do tipo T, copiando em um std::pair Objeto, que posteriormente é copiado (novamente) para o novo elemento do mapa (assumindo que ele já não estava lá). Obviamente, isso produzirá alguns intermediários T objetos. A destruição desses objetos intermediários é o que você observa em seu experimento. Você sente falta da construção deles, já que não gera nenhum feedback do constitutor de cópia da sua classe.

O número exato de objetos intermediários pode depender dos recursos de otimização do compilador; portanto, os resultados podem variar.

Outras dicas

Você tem algumas cópias invisíveis sendo feitas:

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

Resulta em:

Marker One
TestStruct Constructor!
TestStruct copy Constructor!
TestStruct copy Constructor!
TestStruct Destructor!
TestStruct Destructor!
Marker Two
TestStruct Destructor!

Adicione o seguinte à interface do TestStruct:

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

Suas duas chamadas misteriosas de destruidores são provavelmente emparelhadas com chamadas de construtor de cópias em algum lugar dentro do std::map. Por exemplo, é concebível que operator[] O padrão constrói um temporário TestStruct objeto e, em seguida, copie-o para o local adequado no mapa. A razão pela qual existem duas chamadas de destruidores (e, portanto, provavelmente duas chamadas de construtor de cópias) é específico da implementação e dependerá do seu compilador e implementação padrão da biblioteca.

operator[] insere para o map Se ainda não existe um elemento lá.

O que você está faltando é a saída do construtor de cópias fornecido pelo compilador em seu TestStruct, que é usado durante as tarefas domésticas de contêineres. Adicione essa saída e tudo deve fazer mais sentido.

Editar: A resposta de Andrey me levou a dar uma olhada na fonte no Microsoft VC ++ 10's <map>, o que é algo que você também pode fazer para seguir isso com todos os seus detalhes sangrentos. Você pode ver o insert() Ligue para o qual ele se refere.

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

Portanto, a lição é - não coloque estruturas em um mapa se você se preocupa com os ciclos de vida deles. Use ponteiros, ou ainda melhor compartilhados_ptrs para eles

Você pode conferir com este código mais simples.

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

O resultado abaixo mostra que (1) o código de linha acima faz (1 const padrão) e (2 chamadas de cópia padrão).

user const
default const        // here
default copy const   // here
default copy const   // here
dest
dest
dest
dest
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top