Почему std :: begin и std :: end "не безопасны для памяти"?

В этом посте Эрик Ниблер утверждает, что:

Что не так с std :: begin и std :: end? Сюрприз! они не безопасны для памяти. Посмотрите, что делает этот код:

extern std::vector<int> get_data();
auto it = std::begin(get_data());
int i = *it; // BOOM

std :: begin имеет две перегрузки для константных и неконстантных l-значений. Проблема в том, что rvalues связываются с константными lvalue ссылками, что приводит к висячему итератору, описанному выше.

У меня проблемы с пониманием его точки зрения и почему it свисающая ссылка. Может кто-нибудь объяснить?

Ответы

Ответ 1

Функция get_data возвращает объект. При использовании показанного выше способа этот объект будет временным объектом, который будет уничтожен после завершения полного выражения. Теперь итератор ссылается на векторный объект, который больше не существует и не может быть разыменован или использован любым полезным способом.

Ответ 2

Я думаю, что Эрик говорит о std::begin том, что он молча принимает контейнер rvalue в качестве аргумента для начала. На первый взгляд, проблема с кодом также иллюстрируется в

auto it = get_data().begin();

Но std::begin - это бесплатный шаблон функции, его можно сделать так, чтобы он отклонял значения без необходимости добавлять соответствующие ссылочные квалификаторы к каждому элементу begin контейнера. При "простой" пересылке упускается возможность добавить уровень безопасности памяти в код.

В идеале, набор перегрузки мог бы выиграть от добавления

template< class C > 
void begin( C&& ) = delete;

Это привело бы к тому, что код в посте блога был бы полностью отклонен.

Ответ 3

Временный вектор, возвращаемый get_data выходит из области видимости после выполнения std::begin. Он не поддерживается, поэтому it является итератором в уничтоженном объекте.

Ответ 4

Справедливо ли говорить, что это сбой безопасности памяти в std :: begin? Не существует "безопасного" способа создания итератора, который позволял бы следовать указателю после удаления контейнера.

В нем отсутствует проверка, которую имеет другой метод, но это не значит, что нет способа заставить итератор, полученный из диапазонов, работать. Вы просто должны попробовать немного усерднее...

Ответ 5

Потому что это позволяет инициализацию из rvalue, что плохо.

Ответ 6

Фактически, сложная функция может быть упрощена до двух коротких функций, таких как:

int& foo(int x){
   return x;
}
int generate_a_int(){
   return 42;
}

а затем вызвать его foo(generate_a_int()), создается временное значение, после выхода из тела функции временное значение, сгенерированное generate_a_int(), уничтожается, и затем возникает ссылка на мотаться.

Теперь ты понимаешь?