2012-04-14 14 views
18

Wiem, że ogólnie czas życia tymczasowej pętli for z zakresu jest rozszerzony na całą pętlę (czytałem C++11: The range-based for statement: "range-init" lifetime?). Dlatego robią rzeczy, jak to jest ogólnie OK:obiekt tymczasowy w zakresie dla

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

Teraz jestem potykając o problemach z pamięcią, gdy próbuję coś zrobić, pomyślałem być podobny z Qt QList pojemniku:

#include <iostream> 
#include <QList> 

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

tu problem jest to, że valgrind pokazuje nieprawidłowy dostęp do pamięci gdzieś wewnątrz klasy QList. Jednak modyfikując przykład tak, że lista jest przechowywana w zmiennej zapewnia prawidłowy wynik:

#include <iostream> 
#include <QList> 

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

Teraz moje pytanie: robię coś głupiego w pierwszym przypadku, w wyniku np niezdefiniowane zachowanie (nie mam wystarczającego doświadczenia w czytaniu standardu C++, aby odpowiedzieć na to pytanie)? A może to jest problem z korzystaniem z QList lub z implementacji QList?

Odpowiedz

12

Ponieważ używasz C++ 11, you could use initialization list instead. To minie valgrind:

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

Problemem nie jest całkowicie związana z wahać oparte na C++ lub nawet 11. Poniższy kod pokazuje ten sam problem:

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

czyli

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

Nieprawidłowa odczytu jest dlatego, że tymczasowy obiekt z wyrażeniem S() (lub QList<int>{}) jest zniszczona po deklaracji (po C++ 03 i C++ 11 § 12.2/5), ponieważ kompilator nie ma pojęcia, że ​​metoda ta zwróci obiekt tymczasowy. Więc odnosisz się teraz do treści uwolnionej pamięci.

+0

Dzięki za wyjaśnienie. I głupie, oczywiście, że powinienem był najpierw użyć listy inicjalizacyjnej - jakoś po prostu o tym nie myślałem. Prawdopodobnie dzięki przykładom Qt zawsze używałem '<<' w podobnych przypadkach. –

+0

No cóż, wygląda na to, że obsługa C++ 11 jest dostępna tylko w Qt 4.8 i późniejszych. Ale w takich przypadkach mogę z łatwością użyć kontenerów ze standardowej biblioteki. –

+0

Czy ten problem można obejść przez rzutowanie na 'QList const &' (tj.pisanie 'dla (auto i: static_cast const &> (QList {} << 1 << 2 << 3))')? W ten sposób byłby on powiązany z odwołaniem do stałej w inicjalizacji pętli for, jeśli dobrze przeczytałem § 6.5.4, a to z kolei wydłużałoby czas życia tymczasowego do zakresu pętli. –

6

Kompilator nie może wiedzieć, że odwołanie będące wynikiem trzech wywołań do operator << jest powiązane z obiektem tymczasowym QList<int>{}, więc okres ważności tymczasowego nie jest przedłużany. Kompilator nie wie (i nie można się tego spodziewać) niczego o zwracanej wartości funkcji, z wyjątkiem jej typu. Jeśli jest to odniesienie, nie wie, do czego może się przyłączyć. Jestem prawie pewien, że aby zasada przedłużająca życie miała zastosowanie, wiązanie musi być bezpośrednie.

To powinno działać, ponieważ lista nie jest tymczasowy:

#include <iostream> 
#include <QList> 

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

I to powinno działać, ponieważ wiązanie jest bezpośrednia, więc reguła może składać wnioski:

#include <iostream> 
#include <QList> 

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