Почему 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()
, уничтожается, и затем возникает ссылка на мотаться.
Теперь ты понимаешь?