#новичкам
Когда говорят, что в С++ легко отстрелить себе конечность, это не просто слова. Делается это в отдельных случаях почти играючи:
struct Foo {
Foo(std::vector<int> && vec) : items_{std::move(vec)} {}
std::vector<int>& items() { return items_; }
~Foo() { std::cout << "delete" << std::endl; }
private:
std::vector<int> items_;
};
Foo generateData() {
return Foo{std::vector{1, 2, 3, 4, 5}};
}
for (int x : generateData().items()) {
process(x);
}Все очень просто: есть функция, возвращающая объект, содержащий коллекцию, и мы хотим обработать эту коллекцию. Хотим хорошего, но с размаха получаем UB в челюсть. За що?
Перед ответом экскурс в стандарт. Есть у вас range-based for:
for(const auto& item: <range>) {
process(item);
}Range-based for - это по сути сахар, чтобы не писать много кода. И вот во что он разворачивается:
auto&& range = <range>;
for (auto it = range.begin(), end = range.end(); it != end ; ++it)
{
const auto& item = *it;
process(item);
}
Если <range> - это временный объект, то цикл продлевает его время жизни. Но если для вычисления <range> использовался какой-то другой временный объект, то время его жизни уже не продлевается.
std::vector<int> generate() {
return {1, 2, 3, 4, 5};
}
for (const auto& item: generate()) {
...
}
// range-based for transforms into
auto&& range = generate();
for (auto it = range.begin(), end = range.end(); it != end ; ++it)
{
const auto& item = *it;
...
}В примере сверху как раз продлевается время жизни вектора, возвращенного по значению из generate.
А вот во что преобразуется цикл из самого первого примера поста:
auto&& range = generateData().items();
for (auto it = range.begin(), end = range.end(); it != end ; ++it)
{
int x = *it;
process(x);
}
range биндится лишь к ссылке на внутреннее поле Foo, но не продлевает время жизни временного объекта, возвращенного из generateData(). Поэтому он спокойно уничтожится до цикла, который будет оперировать уже висячими ссылками.
Решается проблема несколькими способами. Самый простой - надо создать lvalue объект:
Foo generateData() {
return Foo{std::vector{1, 2, 3, 4, 5}};
}
auto data = generateData();
for (int x : data.items()) {
process(x);
}Другие решения рассмотрим в следующих постах.
Avoid dangling references. Stay cool.
#cppcore #cpp11