Временный объект в диапазоне для
Я знаю, что в целом время жизни временного в цикле for
на основе диапазона распространяется на весь цикл (я читал С++ 11: основанный на диапазоне для оператора: "время-init-время жизни"?). Поэтому делать такие вещи обычно хорошо:
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;
}
Теперь мой вопрос: я делаю что-то немое в первом случае, в результате чего, например, undefined поведение (у меня недостаточно опыта чтения стандарта С++, чтобы ответить на это для себя)? Или это проблема с тем, как я использую QList
или как QList
реализовано?
Ответы
Ответ 1
Поскольку вы используете С++ 11, вместо этого вы можете использовать список инициализации. Это пройдет valgrind:
int main() {
for (auto i : QList<int>{1, 2, 3})
std::cout << i << std::endl;
return 0;
}
Проблема не полностью связана с диапазоном для или даже с С++ 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>{}
) разрушается после объявления (после С++ 03 и С++ 11 §12.2/5), поскольку у компилятора нет что метод foo()
(или operator<<
) вернет этот временный объект. Итак, теперь вы ссылаетесь на содержимое освобожденной памяти.
Ответ 2
Компилятор не может знать, что ссылка, которая является результатом трех вызовов 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;
}