2010-10-25 6 views
8

He creado un caso de prueba simple que muestra un comportamiento extraño que he notado en una base de códigos más grande en la que estoy trabajando. Este caso de prueba está abajo. Confío en el operador "[]" del Mapa STL para crear un puntero a una estructura en un mapa de tales estructuras. En el caso de prueba a continuación, la línea ...En un mapa STL de estructuras, ¿por qué el operador "[]" hace que se invoque el dtor de la estructura 2 veces extra?

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

... me da el puntero (y crea una nueva entrada en el mapa). Lo extraño que he notado es que esta línea no solo crea una nueva entrada en el mapa (debido al operador "[]"), sino que por alguna razón hace que el destructor de la estructura sea llamado dos veces más. Obviamente me falta algo, ¡cualquier ayuda es muy apreciada! Gracias!

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

el código anterior genera el siguiente ...

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

... pero no entiendo lo que hace que las dos primeras invocaciones del destructor de TestStruct? (Creo que la última invocación destructor tiene sentido porque testStructMap va fuera de alcance.)

+0

La respuesta está por debajo. Pero ponga el nivel de optimización tan alto como sea posible y vea cuántas copias se eliminan. –

+0

Más detalles sobre esta pregunta y el por qué detrás de ella se pueden encontrar en esta pregunta: https://stackoverflow.com/questions/26021118/why-doesnt-c-stdmapoperator-use-inplace-new – srm

Respuesta

18

La funcionalidad de std::map<>::operator[] es equivalente a

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

expresión, tal como se especifica en el idioma especificación. Esto, como puede ver, implica la construcción predeterminada de un objeto temporal de tipo T, copiándolo en un objeto std::pair, que luego se copia (nuevamente) en el nuevo elemento del mapa (suponiendo que ya no estuviera allí).Obviamente, esto producirá algunos objetos intermedios T. La destrucción de estos objetos intermedios es lo que observas en tu experimento. Echas de menos su construcción, ya que no generas ningún comentario del constructor de copias de tu clase.

El número exacto de objetos intermedios puede depender de las capacidades de optimización del compilador, por lo que los resultados pueden variar.

5

añadir lo siguiente a la interfaz del TestStruct:

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

operator[] insertos a la map si no es ya un elemento que hay .

Lo que hace falta es la salida para el constructor de copias proporcionado por el compilador en su TestStruct, que se utiliza durante el mantenimiento del contenedor. Agregue esa salida, y todo debería tener más sentido.

EDIT: La respuesta de Andrey me impulsó a echar un vistazo a la fuente en Microsoft VC++ 10 de <map>, que es algo que también se puede hacer para seguir esto a través de toda su sangriento detalle. Puede ver la llamada insert() a la que se refiere.

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

Sus dos llamadas destructor misteriosos probablemente están emparejados con copia constructor llama pasando en algún lugar dentro de la std::map. Por ejemplo, es concebible que operator[] establezca por defecto un objeto temporal TestStruct y luego lo copie en la ubicación correcta en el mapa. La razón por la que hay dos llamadas al destructor (y, por lo tanto, probablemente dos llamadas al constructor de copia) es específica de la implementación y dependerá de su compilador y de la implementación de la biblioteca estándar.

8

tiene algunas copias invisibles están realizando:

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

Resultados en:

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

por lo que la lección es: no coloque las estructuras en un mapa si se preocupa por sus ciclos de vida. Utilice punteros, o incluso mejores shared_ptrs para ellos

0

Puede consultarlo a través de este código más simple.

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

El resultado a continuación muestra que (1) código de línea por encima de las marcas (1 defecto const) y (const copia 2 por defecto) llamadas.

user const 
default const  // here 
default copy const // here 
default copy const // here 
dest 
dest 
dest 
dest 
Cuestiones relacionadas