2012-04-14 12 views
18

Sé que, en general, la duración de un bucle temporal en un rango for se extiende a todo el bucle (he leído C++11: The range-based for statement: "range-init" lifetime?). Por lo tanto haciendo cosas como esta es generalmente aceptable:objeto temporal en rango para

for (auto &thingy : func_that_returns_eg_a_vector()) 
    std::cout << thingy; 

Ahora estoy tropezando sobre problemas de memoria cuando trato de hacer algo que me pareció ser similar con QList contenedor de Qt:

#include <iostream> 
#include <QList> 

int main() { 
    for (auto i : QList<int>{} << 1 << 2 << 3) 
    std::cout << i << std::endl; 
    return 0; 
} 

El problema aquí es que valgrind muestra acceso a la memoria no válida en algún lugar dentro de la clase QList. Sin embargo, modificar el ejemplo para que la lista se almacena en la variable proporciona un resultado correcto:

#include <iostream> 
#include <QList> 

int main() { 
    auto things = QList<int>{} << 1 << 2 << 3; 
    for (auto i : things) 
    std::cout << i << std::endl; 
    return 0; 
} 

Ahora mi pregunta es: ¿Estoy haciendo algo tonto en el primer caso, por ejemplo, lo que resulta en comportamiento indefinido (no tengo suficiente experiencia leyendo el estándar C++ para poder responder esto por mi cuenta) ¿O es esto un problema con la forma en que uso QList, o cómo se implementa QList?

Respuesta

12

Dado que está utilizando C++ 11, you could use initialization list instead. Esto pasará valgrind:

int main() { 
    for (auto i : QList<int>{1, 2, 3}) 
    std::cout << i << std::endl; 
    return 0; 
} 

El problema no está totalmente relacionado con variar basado en para o incluso C++ 11. El código siguiente muestra el mismo problema:

QList<int>& things = QList<int>() << 1; 
things.end(); 

o:

#include <iostream> 

struct S { 
    int* x; 

    S() { x = NULL; } 
    ~S() { delete x; } 

    S& foo(int y) { 
     x = new int(y); 
     return *this; 
    } 
}; 

int main() { 
    S& things = S().foo(2); 
    std::cout << *things.x << std::endl; 
    return 0; 
} 

la lectura no válida se debe a que el objeto temporal de la expresión S() (o QList<int>{}) se destruye después de la declaración (C++ siguiente 03 y C++ 11 §12.2/5), porque el compilador no tiene idea de que el método foo() (o operator<<) devolverá ese objeto temporal. Entonces, ahora se refiere al contenido de la memoria liberada.

+0

Gracias por la aclaración. Y estúpido de mí, por supuesto que debería haber usado una lista de inicialización en primer lugar, de alguna manera simplemente no pensé en ello. Probablemente debido a los ejemplos de Qt siempre usando '<<' para casos similares. –

+0

Ah, bueno, parece que la compatibilidad con C++ 11 solo está disponible en Qt 4.8 y versiones posteriores. Pero en casos como este, puedo usar fácilmente los contenedores de la biblioteca estándar. –

+0

¿Se podría eludir este problema lanzando a 'QList const &' (es decirescribiendo 'for (auto i: static_cast const &> (QList {} << 1 << 2 << 3))')? De esta forma, estaría ligado a una referencia constante en la inicialización del bucle 'for', si leo el §6.5.4 correctamente, y eso a su vez extendería la vida útil temporal al alcance del bucle. –

6

El compilador no puede saber que la referencia que es el resultado de tres llamadas a operator << está vinculada al objeto temporal QList<int>{}, por lo que la vida del temporal no se extiende. El compilador no sabe (y no se puede esperar que sepa) nada sobre el valor de retorno de una función, excepto su tipo. Si se trata de una referencia, no sabe a lo que se puede vincular. Estoy bastante seguro de que, para que se aplique la regla de extender la vida, el enlace debe ser directo.

Esto debería funcionar porque la lista ya no es un temporal:

#include <iostream> 
#include <QList> 

int main() { 
    auto things = QList<int>{}; 
    for (auto i : things << 1 << 2 << 3) 
    std::cout << i << std::endl; 
    return 0; 
} 

Y esto debería funcionar, porque la unión es directa, por lo que la regla se puede aplicar:

#include <iostream> 
#include <QList> 

int main() { 
    for (auto i : QList<int>{1, 2, 3}) 
    std::cout << i << std::endl; 
    return 0; 
} 
Cuestiones relacionadas