2008-10-14 8 views
5

Estaba leyendo un poco en mi C++, y encontré este artículo sobre RTTI (identificación del tipo de tiempo de ejecución): http://msdn.microsoft.com/en-us/library/70ky2y6k(VS.80).aspx. Bueno, ese es otro tema :) - Sin embargo, me encontré con un dicho extraño en la clase type_info, a saber, sobre el ::name -metodo. Dice: "La función de miembro type_info::name devuelve const char* a una cadena terminada en nulo que representa el nombre del tipo legible para el ser humano. La memoria apuntada se almacena en caché y nunca se debe desasignar directamente."Almacenar en caché un const char * como un tipo de devolución

¿Cómo puede implementar algo como esto usted mismo? He estado luchando bastante con este problema exacto a menudo antes, ya que no quiero hacer un nuevo arreglo de char para que la persona que llama elimine, así que me he limitado a std::string hasta el momento.

Por lo tanto, en aras de la simplicidad, supongamos que quiero hacer un método que devuelve "Hello World!", vamos a llamarlo

const char *getHelloString() const; 

En lo personal, me gustaría hacer de alguna manera como esto (Pseudo):

const char *getHelloString() const 
{ 
    char *returnVal = new char[13]; 
    strcpy("HelloWorld!", returnVal); 

    return returnVal 
} 

.. Pero esto significaría que la persona que llama debe hacer un delete[] en mi puntero de retorno :(

Thx de antemano

Respuesta

23

¿Qué tal esto:

const char *getHelloString() const 
{ 
    return "HelloWorld!"; 
} 

Volviendo un literal significa directamente el espacio de la cadena se asigna en el almacenamiento estático por el compilador y estará disponible durante toda la duración del programa.

+0

Este apareció justo cuando estaba a punto de publicar una solución casi idéntica :) – workmad3

+0

suena razonable , pero ¿no será eso, en la práctica, lo mismo que una nueva llamada? Quiero decir, ¿tendrías una cadena que se asignará durante toda la vida de mi aplicación? ¿Qué sucede si llama a este método toneladas de veces? ¿O qué pasa si el valor de retorno no es conocido en tiempo de compilación? – Meeh

+0

La cadena se ubicará en el almacenamiento estático. No habrá asignación de memoria ni desasignación con esta cadena, y también cualquier intento de eliminar una cadena en almacenamiento estático es un comportamiento indefinido. Llamar al método muchas veces solo devolverá el mismo puntero al almacenamiento estático. – workmad3

1

Creo que ya que saben que hay un número finito de estos, simplemente los guardan para siempre. Puede ser apropiado que lo haga en algunos casos, pero como regla general, std :: string va a ser mejor.

También pueden buscar nuevas llamadas para ver si ya hicieron esa cadena y devolver el mismo puntero. De nuevo, dependiendo de lo que estés haciendo, esto también puede ser útil para ti.

+0

Eso suena muy probable ... Así que supongo que no hay una manera inteligente de hacerlo. , pero en este caso, fue un movimiento inteligente por parte de MS evitar demasiados gastos generales :) Ty – Meeh

-3

Es probable que hace usando un buffer estático:

const char* GetHelloString() 
{ 
    static char buffer[256] = { 0 }; 
    strcpy(buffer, "Hello World!"); 
    return buffer; 
} 

Este buffer es como una variable global que sólo es accesible desde esta función.

+0

esto solo funcionará según lo previsto cuando la función está diseñada para devolver siempre la misma cadena ... y en ese caso un literal de cadena es mucho mejor. – smerlin

2

Bueno, si estamos hablando solo de una función, que siempre queremos devolver el mismo valor. es bastante simple.

const char * foo() 
{ 
    static char[] return_val= "HelloWorld!"; 
    return return_val; 
} 

El poco complicado es cuando usted empezar a hacer cosas donde está el almacenamiento en caché el resultado, y entonces usted tiene que considerar que rosca, o cuando la memoria caché se invalida, y tratando de guardar cosa en almacenamiento local de subprocesos. Pero si se trata de una producción única que se copia de inmediato, esto debería ser el truco.
Alternativamente, si no tiene un tamaño fijo, tiene que hacer algo donde tenga que usar un búfer estático de tamaño arbitrario ... en el que eventualmente podría tener algo demasiado grande, o dirigirse a una clase administrada, digamos std::string.

const char * foo() 
{ 
    static std::string output; 
    DoCalculation(output); 
    return output.c_str(); 
} 

también la firma de la función

const char *getHelloString() const; 

sólo es aplicable para las funciones miembro. En ese momento no es necesario tratar con variables locales de funciones estáticas y solo podría usar una variable miembro.

+0

Hehe, great answer - ty :) – Meeh

+0

Nice saluda a los chicos que intentan usar tu función en contextos de subprocesos múltiples después de eso :-) – mmmmmmmm

+0

Dije que Threading se vuelve complicado. – Dan

-5

No puede confiar en GC; esto es C++ Eso significa que debe mantener la memoria disponible hasta que finalice el programa. Simplemente no sabe cuándo es seguro eliminar []. Entonces, si quiere construir y devolver un const char *, simple new [] it y devolverlo. Acepta la fuga inevitable.

+0

Creo que ha confundido un montón de cosas aquí. La pregunta no es si es seguro eliminar el const char * (no lo es porque la función que llama explícitamente lo dice), sino más bien cómo crear una función de este tipo. –

+0

Gracias - Voy a reformular – MSalters

+0

Esta es la peor sugerencia de la historia. La función de la que habla es aquella que "posee" la memoria que devuelve y, por lo tanto, el usuario no debe liberarla. Si usted mismo crea una función que devuelve un nuevo [] y libera la propiedad (es decir, nunca la elimina []), es simplemente una mala codificación. ¡Y completamente evitable! –

0

¿Por qué el tipo de devolución debe ser const? No piense en el método como obtener método, piense en él como crear método. He visto muchas API que requieren que elimines algo que devuelve un operador/método de creación. Solo asegúrate de anotar eso en la documentación.

/* create a hello string 
* must be deleted after use 
*/ 
char *createHelloString() const 
{ 
    char *returnVal = new char[13]; 
    strcpy("HelloWorld!", returnVal); 

    return returnVal 
} 
0

Lo que a menudo he hecho cuando necesito este tipo de funcionalidad es tener un puntero char * en la clase - inicializado a cero - y asignar cuando sea necesario.

a saber:

class CacheNameString 
{ 
    private: 
     char *name; 
    public: 
     CacheNameString():name(NULL) { } 

    const char *make_name(const char *v) 
    { 
     if (name != NULL) 
      free(name); 

     name = strdup(v); 

     return name; 
    } 

}; 
0

Algo como esto haría:

const char *myfunction() { 
    static char *str = NULL; /* this only happens once */ 
    delete [] str; /* delete previous cached version */ 
    str = new char[strlen("whatever") + 1]; /* allocate space for the string and it's NUL terminator */ 
    strcpy(str, "whatever"); 
    return str; 
} 

EDIT: Algo que se me ocurrió es que un buen reemplazo para esto podría ser devuelve un impulso :: shared_pointer lugar. De esta forma, la persona que llama puede conservarlo todo el tiempo que quiera y no tiene que preocuparse por eliminarlo explícitamente. Un compromiso justo IMO.

+0

'const char * ptr1 = myfunction(); const char * ptr2 = myfunction();/* ahora el uso de ptr1 causa un comportamiento indefinido * /' – smerlin

+0

de hecho, solo diría "do not do that" y es ' Estaré bien. –

1

Tenga cuidado al implementar una función que asigna un trozo de memoria y luego espera que la persona que llama para desasignar que, como lo hace en el OP:

const char *getHelloString() const 
{ 
    char *returnVal = new char[13]; 
    strcpy("HelloWorld!", returnVal); 

    return returnVal 
} 

De esta manera la propiedad que va a transferir de la memoria de llamador. Si se llama a este código de alguna otra función:

int main() 
{ 
    char * str = getHelloString(); 
    delete str; 
    return 0; 
} 

... la semántica de la transferencia de la propiedad de la memoria no es clara, creando una situación en la que los errores y pérdidas de memoria son más probables.

Además, al menos en Windows, si las dos funciones están en 2 módulos diferentes, podría dañar el montón. En particular, si main() está en hello.exe, compilado en VC9 y getHelloString() está en utility.dll, compilado en VC6, corromperá el montón cuando elimine la memoria. Esto se debe a que VC6 y VC9 usan su propio montón, y no son el mismo montón, por lo que está asignando de un montón y desasignando de otro.

0

El consejo dado que advierte sobre la vida útil de la cadena devuelta es un consejo sensato. Siempre debe tener cuidado al reconocer sus responsabilidades cuando se trata de administrar la vida útil de los punteros devueltos. Sin embargo, la práctica es bastante segura, siempre que la variable apuntada dure más que la llamada a la función que la devolvió. Considere, por ejemplo, el puntero a const char devuelto por c_str() como un método de la clase std::string.Esto devuelve un puntero a la memoria administrada por el objeto de cadena que se garantiza que es válida siempre que el objeto de cadena no se elimine o se haga para reasignar su memoria interna.

En el caso de la clase std::type_info, es una parte del estándar C++ como lo implica su espacio de nombres. La memoria devuelta desde name() apunta en realidad a la memoria estática creada por el compilador y el enlazador cuando se compiló la clase y es parte del sistema de identificación de tipo de tiempo de ejecución (RTTI). Como se refiere a un símbolo en el espacio del código, no debe intentar eliminarlo.

3

Me gustan todas las respuestas sobre cómo la cadena se puede asignar estáticamente, pero eso no es necesariamente cierto para todas las implementaciones, especialmente aquella a la que se vinculó el cartel original. En este caso, parece que el nombre del tipo decorado se almacena estáticamente para ahorrar espacio, y el nombre del tipo no decorado se calcula bajo demanda y se almacena en caché en una lista vinculada.

Si tiene curiosidad acerca de cómo la implementación de Visual C++ type_info::name() asigna y almacena en caché su memoria, no es difícil de averiguar. En primer lugar, crear un pequeño programa de prueba:

#include <cstdio> 
#include <typeinfo> 
#include <vector>  
int main(int argc, char* argv[]) { 
    std::vector<int> v; 
    const type_info& ti = typeid(v); 
    const char* n = ti.name(); 
    printf("%s\n", n); 
    return 0; 
} 

Construir y ejecutarlo con un depurador (utilicé WinDbg) y mirar el puntero devuelto por type_info::name(). ¿Señala a una estructura global? Si es así, la orden de WinDBG ln le dirá el nombre del símbolo más cercano:

0:000> ?? n 
char * 0x00000000`00857290 
"class std::vector<int,class std::allocator<int> >" 
0:000> ln 0x00000000`00857290 
0:000> 

ln no se imprime nada, lo que indica que la cadena no estaba en el rango de direcciones propiedad de cualquier módulo específico. Estaría en ese rango si estuviera en el segmento de datos o de solo lectura. Vamos a ver si se le asignó en el montón, mediante la búsqueda en todos los montones para la dirección devuelta por type_info::name():

0:000> !heap -x 0x00000000`00857290 
Entry    User    Heap    Segment    Size PrevSize Unused Flags 
------------------------------------------------------------------------------------------------------------- 
0000000000857280 0000000000857290 0000000000850000 0000000000850000  70  40  3e busy extra fill 

Sí, se asignaron en el montón. Poner un punto de interrupción al comienzo de malloc() y reiniciar el programa lo confirma.

En cuanto a la declaración de <typeinfo> da una pista acerca de dónde los punteros montón están siendo cacheados:

struct __type_info_node { 
    void *memPtr; 
    __type_info_node* next; 
}; 

extern __type_info_node __type_info_root_node; 
... 
_CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const; 

Si encuentra la dirección de __type_info_root_node y camina por la lista en el depurador, a encontrar rápidamente un nodo que contiene la misma dirección devuelta por type_info::name(). La lista parece estar relacionada con el esquema de almacenamiento en caché.

La página MSDN vinculada en la pregunta original parece llenar los espacios en blanco: el nombre se almacena en su forma decorada para ahorrar espacio, y este formulario es accesible a través del type_info::raw_name(). Cuando llama al type_info::name() por primera vez en un tipo determinado, descifra el nombre, lo almacena en un búfer asignado en el montón, almacena en caché el puntero del búfer y lo devuelve.

La lista vinculada también se puede utilizar para desasignar las cadenas en caché durante la salida del programa (sin embargo, no verifiqué si ese es el caso). Esto garantizaría que no aparezcan como pérdidas de memoria cuando ejecuta una herramienta de depuración de memoria.

+0

+1 para una respuesta de tres años. Pero sigue siendo informativo (acabo de localizar una fuente de algunas pérdidas de memoria con el uso de 'typeinfo :: name()'). – icabod

0

Creo que algo así solo se puede implementar "limpiamente" usando objetos y el modismo RAII. Cuando se llama al destructor de objetos (obj queda fuera del alcance), podemos suponer con seguridad que los punteros const char* ya no se usan.

código de ejemplo:

class ICanReturnConstChars 
{ 
    std::stack<char*> cached_strings 
    public: 
    const char* yeahGiveItToMe(){ 
     char* newmem = new char[something]; 
     //write something to newmem 
     cached_strings.push_back(newmem); 
     return newmem; 
    } 
    ~ICanReturnConstChars(){ 
     while(!cached_strings.empty()){ 
      delete [] cached_strings.back() 
      cached_strings.pop_back() 
     } 
    } 
}; 

La única otra posibilidad que conozco es que pasar un smart_ptr ..

Cuestiones relacionadas