2009-02-20 15 views
19

Estoy observando un comportamiento extraño de std :: map :: clear(). Se supone que este método llama al destructor del elemento cuando se llama, sin embargo, aún se puede acceder a la memoria después de llamar a clear().¿Por qué la memoria sigue siendo accesible después de llamar a std :: map :: clear()?

Por ejemplo:

struct A 
{ 
    ~A() { x = 0; } 
    int x; 
}; 

int main(void) 
{ 
    std::map< int, A * > my_map; 
    A *a = new A(); 
    a->x = 5; 
    my_map.insert(std::make_pair< int, *A >(0, a)); 

    // addresses will be the same, will print 5 
    std::cout << a << " " << my_map[0] << " " << my_map[0]->x << std::endl; 

    my_map.clear(); 

    // will be 0 
    std::cout << a->x << std::endl; 

    return 0; 
} 

La pregunta es, ¿por qué es variable a todavía accesibles después de su destructor fue llamado por mapa :: clear()? ¿Debo escribir delete a; después de llamar al my_map.clear() o es seguro sobrescribir los contenidos del a?

Gracias de antemano por su ayuda, sneg

+0

Intentar usar at() para acceder a él no le permitirá acceder a algo que no está allí, por ejemplo: http://ideone.com/ZeRTnd (aunque el elemento con la clave "B" se definió anteriormente, después de map.clear () al intentar acceder a la clave "B" obtienes la excepción '' std :: out_of_range'' –

Respuesta

19

std :: map no gestiona la memoria apuntada por los valores del puntero - depende de usted hacerlo usted mismo. Si no desea utilizar punteros inteligentes, se puede escribir un propósito general libre de & función clara como esto:

template <typename M> void FreeClear(M & amap) 
    for (typename M::iterator it = amap.begin(); it != amap.end(); ++it) { 
     delete it->second; 
    } 
    amap.clear(); 
} 

y utilizarla:

std::map< int, A * > my_map; 
// populate 
FreeClear(my_map) 

;

+1

Sugiero agregar un enlace a una pregunta sobre punteros inteligentes y/o para aumentar :: shared_ptr. –

19

Si almacena punteros en un mapa (o una lista, o algo por el estilo) USTED son los responsables de la eliminación de los punteros, ya que el mapa doesn' Saber si han sido creados con nuevos o no. La función borrar solo invoca destructores si no utiliza punteros.

Ah, y una cosa más: invocar un destructor (o incluso llamar a eliminar) no significa que ya no se puede acceder a la memoria. Solo significa que accederá a la basura si lo hace.

+5

Solo significa que puedes tener acceso a la basura, pero a veces accedes a un área que aún no se ha convertido en basura, por lo que parece funcionar para un tiempo. Por eso "pero funciona, así que puedo salirse con la tuya" es totalmente falso. –

4

Eso es porque map.clear() llama destructores de los datos contenidos en el mapa, en su caso, del puntero a a. Y esto no hace nada.

Es posible que desee poner algún tipo de smart pointer en el mapa de la memoria ocupada por a para reclamar automáticamente.

Por cierto, ¿por qué pones los argumentos de la plantilla en la llamada al make_pair? La deducción del argumento de la plantilla debería funcionar bastante bien aquí.

+0

Así que si no uso ningún tipo de clase de punteros inteligentes, ¿necesito pasar por el mapa y 'eliminar' manualmente? cada elemento individual? ¿Cómo puedo borrar el mapa después de eso? Quiero decir, 'clear()' intentará invocar destructores de elementos que ya fueron eliminados. – sneg

+1

Simplemente borre todos los punteros, y luego llame a clear(). Como jpalecek te lo dije, llamando al destructor de un puntero (que es totalmente diferente de llamar al desructor del objeto al que apunta) no hace nada. – Marc

+0

Solo para aclarar: si almacena objetos reales en un mapa, se llamará a su destructor.Pero si almacena punteros (cualquier tipo de puntero), el mapa solo sabrá que tiene un montón de punteros sin sentido, y los tratará como lo que son esencialmente: enteros que representan direcciones de memoria. – Marc

1

Cuando libera una parte de la memoria del montón, su contenido no se pone a cero. Simplemente están disponibles para su asignación nuevamente. Por supuesto, debería considerar que la memoria no es accesible, porque los efectos de acceder a la memoria no asignada no están definidos.

En realidad, impedir el acceso a una página de memoria ocurre en un nivel inferior, y las bibliotecas std no lo hacen.

Cuando asigna memoria con nueva, debe eliminarla usted mismo, a menos que use un puntero inteligente.

0

Cualquier recipiente almacena el tipo de objeto y llamar a los constructores correspondientes: código interno cada nodo puede tener un aspecto similar a:

__NodePtr 
{ 
    *next; 
    __Ty Val; 
} 

Al asignar sucede con la construcción de la Val en función del tipo y la vinculación.Algo similar a:

_Ty _Val = _Ty(); 
_Myhead = _Buynode(); 
_Construct_n(_Count, _Val); 

Cuando lo elimina, llama a los destructores correspondientes.

Cuando almacena referencias (punteros) no llamará a ningún constructor ni se destruirá.

1

Después de haber pasado los últimos 2 meses comiendo, durmiendo y respirando mapas, tengo una recomendación. Deje que el mapa asigne sus propios datos siempre que sea posible. Es mucho más limpio, exactamente por el tipo de razones que está resaltando aquí.

También hay algunas ventajas sutiles, como si está copiando datos de un archivo o socket a los datos del mapa, el almacenamiento de datos existe tan pronto como el nodo existe porque cuando el mapa llama a malloc() para asignar el nodo , asigna memoria tanto para la clave como para los datos. (AKA mapa [clave] .primero y mapa [clave] .segundo)

Esto le permite utilizar el operador de asignación en lugar de memcpy(), y requiere 1 llamada menos a malloc() - el que usted hace.

IC_CDR CDR, *pThisCDRLeafData; // a large struct{} 

    while(1 == fread(CDR, sizeof(CDR), 1, fp)) { 
    if(feof(fp)) { 
     printf("\nfread() failure in %s at line %i", __FILE__, __LINE__); 
    } 
    cdrMap[CDR.iGUID] = CDR; // no need for a malloc() and memcpy() here  
    pThisCDRLeafData = &cdrMap[CDR.iGUID]; // pointer to tree node's data 

Algunas advertencias a tener en cuenta que vale la pena señalar aquí.

  1. no llame a malloc() o nueva en la línea de código que añade el nodo de árbol como su llamada a malloc() devolverá un puntero antes de la llamada del mapa para malloc() ha asignado un lugar para celebrar la regreso de tu malloc().
  2. en modo de depuración, esperamos tener problemas similares al intentar liberar() su memoria. Ambos me parecen problemas de compilación, pero al menos en MSVC 2012, existen y son un problema grave.
  3. piense en dónde "anclar" sus mapas. IE: donde están declarados. No quiere que salgan del alcance por error. main {} siempre es seguro.

    INT _tmain(INT argc, char* argv[]) { 
    IC_CDR  CDR, *pThisCDRLeafData=NULL; 
    CDR_MAP  cdrMap; 
    CUST_MAP custMap; 
    KCI_MAP  kciMap; 
    
  4. que he tenido muy buena suerte, y estoy muy feliz con un mapa crítico asignar una estructura ya que es datos del nodo, y tener esa estructura "ancla" un mapa. Si bien las estructuras anónimas han sido abandonadas por C++ (una horrible y horrible decisión que DEBE revertirse), los mapas que son el primer miembro de la estructura funcionan igual que las estructuras anónimas. Muy pulido y limpio con cero efectos de tamaño. Pasar un puntero a la estructura de hoja, o una copia de la estructura por valor en una llamada a función, ambos funcionan muy bien. Muy recomendable.

  5. puede atrapar los valores de retorno de .insert para determinar si encontró un nodo existente en esa clave o si creó uno nuevo. (Ver # 12 para el código) Usar la notación del subíndice no permite esto. Sería mejor instalarlo y ponerlo, especialmente porque la notación [] no funciona con multimaps. (No tendría sentido hacerlo, ya que no hay una "clave", sino una serie de claves con los mismos valores en un multimap)
  6. puede, y debe, también atrapar las devoluciones para .erase y. empty() (SÍ, es molesto que algunas de estas cosas sean funciones y necesiten el() y algunos, como .erase, do not)
  7. puede obtener tanto el valor clave como el valor de datos para cualquier nodo del mapa usando .primer y .segundo, que todos los mapas, por convención, usan para devolver la clave y los datos respectivamente
  8. ahórrese una GRAN cantidad de confusión y tipeo, y use typedefs para sus mapas, como tal.

    typedef map<ULLNG, IC_CDR>  CDR_MAP;  
    typedef map<ULLNG, pIC_CDR>  CALL_MAP; 
    typedef struct { 
        CALL_MAP callMap; 
        ULNG  Knt;   
        DBL   BurnRateSec; 
        DBL   DeciCents; 
        ULLNG  tThen;  
        DBL   OldKCIKey; 
    } CUST_SUM, *pCUST_SUM; 
    typedef map<ULNG,CUST_SUM> CUST_MAP, CUST_MAP; 
    typedef map<DBL,pCUST_SUM> KCI_MAP; 
    
  9. referencias de paso a los mapas utilizando el typedef y & operador como en

    ULNG DestroyCustomer_callMap(CUST_SUM Summary, CDR_MAP& cdrMap, KCI_MAP& kciMap)

  10. utilizar el "auto" tipo variable para iteradores. El compilador deducirá del tipo especificado en el resto del cuerpo del bucle for() qué tipo de tipo de mapa usará. ¡Está tan limpio que es casi mágico!

    for(auto itr = Summary.callMap.begin(); itr!= Summary.callMap.end(); ++itr) {

  11. definir algunas constantes evidentes para que el regreso de .erase y .empty() más meaningfull.

    if(ERASE_SUCCESSFUL == cdrMap.erase (itr->second->iGUID)) {

  12. dado que "punteros inteligentes" son en realidad sólo mantienen un contador de referencia, recuerde que siempre puede mantener su propia cuenta de referencia, una probablemente en un producto de limpieza, y de manera más evidente. Combinando esto con los números 5 y 10 anteriores, puedes escribir un buen código de limpieza como este.

    #define Pear(x,y) std::make_pair(x,y) // some macro magic 
    
    auto res = pSumStruct->callMap.insert(Pear(pCDR->iGUID,pCDR)); 
    if (! res.second) { 
        pCDR->RefKnt=2; 
    } else { 
        pCDR->RefKnt=1; 
        pSumStruct->Knt += 1; 
    } 
    
  13. usando un puntero a colgar en un nodo de mapa que asigna todo para sí, es decir: no hay punteros de usuario apuntando a malloc usuario() ed objetos, funciona bien, es potencialmente más eficiente, y y ser usado para mutar los datos de un nodo sin efectos secundarios en mi experiencia.

  14. en el mismo tema, como un puntero se puede utilizar de manera muy eficaz para preservar el estado de un nodo, como en pThisCDRLeafData anterior. Pasar esto a una función que muta/cambia los datos de ese nodo particular es más limpio que pasar una referencia al mapa y la clave necesaria para volver al nodo pThisCDRLeafData está apuntando a.

  15. iteradores no son mágicos. Son caros y lentos, ya que estás navegando por el mapa para obtener valores. Para un mapa con un millón de valores, puede leer un nodo basado en una clave a unos 20 millones por segundo. Con los iteradores es probablemente ~ 1000 veces más lento.

Creo que sobre covers por el momento. Se actualizará si algo de esto cambia o si hay ideas adicionales para compartir. Estoy especialmente disfrutando de usar el STL con código C. IE: no es una clase a la vista en ningún lado. Simplemente no tienen sentido en el contexto en el que estoy trabajando, y no es un problema. Buena suerte.

+1

Aunque un poco de compromiso, tener el mapa asignando sus propios datos en lugar de un puntero para mantener el retorno de su malloc() también puede ser más eficiente en la memoria, ya que su malloc() y malloc del mapa requeridos para mantener su los datos serán del mismo tamaño, guardando los requisitos de memoria para el puntero ahora redundante. Los punteros de 64 bits suman mucho almacenamiento. Esto se intercambia con los nodos internos del árbol de asignación de Map/Tree para almacenar cosas que nunca almacenarán, aunque evita que el montón se fragmente, ya que todos los malloc() tienen el mismo tamaño cuando se agregan o eliminan nodos. – RocketRoy

+0

Uno que podría agregar, es usar malloc() en lugar de new(), ya que no tiene sentido asignar memoria memset() con new() cuando está en el proceso de memcpy() ing datos en ese nuevo almacenamiento. Es una pérdida total y completa del tiempo que se necesita para inicializar el almacenamiento. Si desea hacer esto para fines de depuración, use calloc(). El STL utiliza malloc() y free() internamente, por lo que toma esa referencia y ejecuta con ella. – RocketRoy

Cuestiones relacionadas