2009-02-25 14 views
85

¿C++ proporciona una garantía de por vida de una variable temporal que se crea dentro de una llamada a función pero no se utiliza como parámetro? He aquí un ejemplo de clase:Vida útil garantizada temporal en C++?

class StringBuffer 
{ 
public: 
    StringBuffer(std::string & str) : m_str(str) 
    { 
     m_buffer.push_back(0); 
    } 
    ~StringBuffer() 
    { 
     m_str = &m_buffer[0]; 
    } 
    char * Size(int maxlength) 
    { 
     m_buffer.resize(maxlength + 1, 0); 
     return &m_buffer[0]; 
    } 
private: 
    std::string & m_str; 
    std::vector<char> m_buffer; 
}; 

Y aquí es cómo se usa:

// this is from a crusty old API that can't be changed 
void GetString(char * str, int maxlength); 

std::string mystring; 
GetString(StringBuffer(mystring).Size(MAXLEN), MAXLEN); 

¿Cuándo el destructor del objeto temporal StringBuffer ser llamado? Es esto:

  • ¿Antes de la llamada a GetString?
  • ¿Después de que GetString regrese?
  • ¿Depende del compilador?

Sé que C++ garantiza que una variable temporal local será válida siempre que haya una referencia a ella: ¿esto se aplica a objetos padre cuando hay una referencia a una variable miembro?

Gracias.

+0

¿por qué no heredar y sobrecargar o hacer una función global? Estaría más limpio y no tendrías que crear una clase para llamar a un miembro. –

+1

Si va a usar esto, debe llamar a 'm_str.reserve (maxlength)' en 'char * Size (int maxlength)', de lo contrario el destructor podría lanzar. – Mankarse

Respuesta

93

El destructor para ese tipo de temporales se llama al final de la expresión completa. Esa es la expresión más externa que no es parte de ninguna otra expresión. Eso es en su caso después de que la función retorna y el valor es evaluado. Por lo tanto, funcionará todo bien.

Es, de hecho, lo que hace que las plantillas de expresión de trabajo: Se puede mantener referencias de retención a ese tipo de provisionales en una expresión como

e = a + b * c/d 

Debido a que cada temporal durará hasta que la expresión

x = y 

Es evaluado completamente Se describe de manera concisa en 12.2 Temporary objects en el Estándar.

+1

Nunca llegué a conseguir una copia del estándar. Debería convertirlo en una prioridad. –

+1

@JohannesSchaub: ¿Qué es una "expresión completa" en este caso: 'printf ("% s ", strdup (std :: string (" $$$ "). C_str()));'? Quiero decir ' strdup (std :: string ("$$$"). c_str()) 'se toma como la expresión completa, luego el puntero que' strdup' ve es * valid *. Si 'std :: string (" $$$ "). C_str()' es una expresión completa, ¡el puntero que 'strdup' ve es * inválido *! ¿Podría explicar un poco más en función de este ejemplo? –

+1

@GrimFandango AIUI todo su 'printf' es la expresión completa. Por lo tanto, el 'strdup' es una fuga de memoria innecesaria; simplemente puede dejar que imprima' c_str() 'directamente. –

4

Después de que regrese la llamada a GetString.

2

StringBuffer está en el alcance de GetString. Debería destruirse al final del alcance de GetString (es decir, cuando vuelva). Además, no creo que C++ garantice que exista una variable siempre que haya una referencia.

A continuación se debe compilar: Respuesta

Object* obj = new Object; 
Object& ref = &(*obj); 
delete obj; 
+0

Creo que exageré la garantía, solo para los temporales locales. Pero existe. –

+0

He editado la pregunta. Sin embargo, de acuerdo con las respuestas proporcionadas hasta ahora, parece ser un punto discutible. –

+0

Todavía no creo que su edición sea correcta: Object & obj = GetObj(); Object & GetObj() {return & Object(); } // malo - dejará la referencia pendiente. – BigSandwich

16

de litb es exacta. El tiempo de vida del objeto temporal (también conocido como valor r) está vinculado a la expresión y el destructor para el objeto temporal se llama al final de la expresión completa y cuando se llama al destructor en StringBuffer, el destructor en m_buffer también será invocado llamado, pero no el destructor en m_str ya que es una referencia.

Tenga en cuenta que C++ 0x cambia las cosas solo un poco porque agrega referencias rvalue y semántica de movimiento. Esencialmente mediante el uso de un parámetro de referencia rvalue (anotado con & &) puedo 'mover' el valor r en la función (en lugar de copiarlo) y el tiempo de vida del valor r puede vincularse al objeto al que se mueve, no a la expresión. Hay una muy buena blog post from the MSVC team on that walks through this in great detail y animo a la gente a leerla.

El ejemplo pedagógico para mover rvalue es cadenas temporales y mostraré la asignación en un constructor. Si tengo un MyType clase que contiene una variable miembro de la cadena, que puede ser inicializado con un valor p en el constructor de este modo:

class MyType{ 
    const std::string m_name; 
public: 
    MyType(const std::string&& name):m_name(name){}; 
} 

Esto es bueno porque cuando me declaro una instancia de esta clase con un objeto temporal:

void foo(){ 
    MyType instance("hello"); 
} 

lo que sucede es que evitar copiar y destruyendo el objeto temporal y "hola" se coloca directamente en el interior variable miembro de la instancia de la clase propietaria. Si el objeto pesa más que una 'cadena', entonces la copia adicional y la llamada al destructor pueden ser importantes.

3

escribí casi exactamente la misma clase:

template <class C> 
class _StringBuffer 
{ 
    typename std::basic_string<C> &m_str; 
    typename std::vector<C> m_buffer; 

public: 
    _StringBuffer(std::basic_string<C> &str, size_t nSize) 
     : m_str(str), m_buffer(nSize + 1) { get()[nSize] = (C)0; } 

    ~_StringBuffer() 
     { commit(); } 

    C *get() 
     { return &(m_buffer[0]); } 

    operator C *() 
     { return get(); } 

    void commit() 
    { 
     if (m_buffer.size() != 0) 
     { 
      size_t l = std::char_traits<C>::length(get()); 
      m_str.assign(get(), l);  
      m_buffer.resize(0); 
     } 
    } 

    void abort() 
     { m_buffer.resize(0); } 
}; 

template <class C> 
inline _StringBuffer<C> StringBuffer(typename std::basic_string<C> &str, size_t nSize) 
    { return _StringBuffer<C>(str, nSize); } 

Antes de la norma hizo cada compilador de manera diferente. Creo que el antiguo Manual de referencia anotado para C++ especificaba que los temporales deberían limpiarse al final del alcance, por lo que algunos compiladores lo hicieron. Todavía en 2003, encontré que ese comportamiento todavía existía por defecto en el compilador Forte C++ de Sun, por lo que StringBuffer no funcionó. Pero me sorprendería si algún compilador actual estuviera tan roto.

+0

¡Qué espeluznantes son! Gracias por la advertencia: el primer lugar que probaré es VC++ 6, que no se conoce por su cumplimiento de estándares. Estaré mirando cuidadosamente. –

+0

Habría escrito la clase originalmente en VC++ 6, así que no debería ser un problema. –

Cuestiones relacionadas