2012-04-14 3 views
18

Я знаю, что в общем случае время жизни временного в цикле for на основе диапазона распространяется на весь цикл (я читал C++11: The range-based for statement: "range-init" lifetime?). Поэтому делать такие вещи, как это, как правило, в порядке:временный объект в диапазоне от

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

Теперь я спотыкаясь о проблемах памяти, когда я пытаюсь сделать что-то я думал, что подобное с Qt в QList контейнере:

#include <iostream> 
#include <QList> 

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

Проблема здесь что valgrind показывает недопустимый доступ к памяти где-то внутри класса QList. Тем не менее, изменения в пример, так что список хранится в переменной дает правильный результат:

#include <iostream> 
#include <QList> 

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

Теперь мой вопрос: я делаю что-то тупую в первом случае, в результате чего, например, Неопределенное поведение (у меня недостаточно опыта чтения стандарта C++, чтобы ответить на это для себя)? Или это проблема с тем, как я использую QList или как реализован QList?

ответ

12

Поскольку вы используете C++ 11, you could use initialization list instead. Это будет проходить Valgrind:

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

Проблема не полностью связаны ранжировать основе для или даже C++ 11. Следующий код демонстрирует ту же проблему:

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

или:

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

недействительно чтение происходит потому, что временный объект из выражения S() (или QList<int>{}) разрушается после объявления (после C++ 03 и C++ 11 §12.2/5), поскольку компилятор не знает, что метод foo() (или operator<<) вернет этот временный объект. Итак, теперь вы ссылаетесь на содержимое освобожденной памяти.

+0

Спасибо за разъяснение. И глупо мне, конечно, я должен был использовать список инициализации в первую очередь - я как-то просто не думал об этом. Вероятно, из-за примеров Qt всегда используется '<<' для подобных случаев. –

+0

Хорошо, похоже, что поддержка C++ 11 доступна только в Qt 4.8 и более поздних версиях. Но для таких случаев я могу легко использовать контейнеры из стандартной библиотеки. –

+0

Может ли эта проблема быть обойдена путем литья в 'QList const &' (т.write 'for (auto i: static_cast const &> (QList {} << 1 << 2 << 3))')? Таким образом, он будет привязан к константной ссылке в инициализации цикла 'for', если я правильно прочитал § 6.5.5, и это, в свою очередь, расширит временное время жизни до объема цикла. –

6

Компилятор не может знать, что ссылка, являющаяся результатом трех вызовов operator <<, привязана к временному объекту QList<int>{}, поэтому срок службы не продлевается. Компилятор не знает (и не может ожидать чего-либо знать) что-либо о возвращаемом значении функции, кроме ее типа. Если это ссылка, она не знает, к чему она может привязываться. Я уверен, что для того, чтобы правило продления жизни было применимо, привязка должна быть прямой.

Это должно работать, потому что список уже не является временным:

#include <iostream> 
#include <QList> 

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

И это должно работать, потому что связывание является прямым, поэтому правило может применяться:

#include <iostream> 
#include <QList> 

int main() { 
    for (auto i : QList<int>{1, 2, 3}) 
    std::cout << i << std::endl; 
    return 0; 
} 
Смежные вопросы