Здравствуйте, Caracrist, Вы писали:
C>is subj. UB?
Почему? operator[] возвращает ссылку, дальше имеем обычную историю с forwarding reference (reference collapsing, это все) в результате получаем emplace_back(T& )
Здравствуйте, andyp, Вы писали:
C>>is subj. UB? A>Почему? operator[] возвращает ссылку, дальше имеем обычную историю с forwarding reference (reference collapsing, это все) в результате получаем emplace_back(T& )
Потому что, если случится переаллокация, то в конструктор объекта может прийти дохлая ссылка, если только в реализации вектора не создается промежуточная копия. Но рассчитывать на это не стоит:
23.3.6.5 vector modifiers
Remarks: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid. If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. If an exception is thrown while inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible<T>::value is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, andyp, Вы писали:
C>>>is subj. UB? A>>Почему? operator[] возвращает ссылку, дальше имеем обычную историю с forwarding reference (reference collapsing, это все) в результате получаем emplace_back(T& )
R>Потому что, если случится переаллокация, то в конструктор объекта может прийти дохлая ссылка, если только в реализации вектора не создается промежуточная копия. Но рассчитывать на это не стоит: R>
R>23.3.6.5 vector modifiers
R>Remarks: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid. If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. If an exception is thrown while inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible<T>::value is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.
Здравствуйте, rg45, Вы писали:
R>Потому что, если случится переаллокация, то в конструктор объекта может прийти дохлая ссылка, если только в реализации вектора не создается промежуточная копия. Но рассчитывать на это не стоит:
Здравствуйте, MT-Wizard, Вы писали:
MW>Не UB, можно всё что явно не запрещено...
Согласно стандарту, все как раз наоборот:
1.3.24 undefined behavior
behavior for which this International Standard imposes no requirements
Как должна повести себя программа в случае переаллокации, никаких требований нет. И будет переаллокация или нет, тоже не известно. Так что UB в чистом виде, если не зарезервировать достаточный объем памяти заранее.
Ради интереса, попробуй выполнить этот пример в Visual Studio (в отладочной конфигурации assert вылетает на первой же итерации):
Здравствуйте, MT-Wizard, Вы писали:
MW>Не UB, можно всё что явно не запрещено. STL рассказывал это где-то, но не могу найти именно ту статью; зато есть косвенное подтверждение тут:
вопрос невнятно сформулирован
если речь о доступе к нулевому элементу пустого вектора, то это UB
если речь об алиасинге, то есть вставляемый элемент уже где-то в векторе есть, то это не UB, но у VS есть\были баги в этом месте
ТС, дай внятный кусок кода, лучше в онлайн компиляторе
Despite appearances, iterator invalidation doesn't prohibit this — when you call push_back(), the value is valid, so the implementation has to ensure that if it triggers a reallocation, it doesn't accidentally invalidate the element.
Это звучит резонно.
Я вижу, название принципа "nothing prohibits it" взято в кавычки в сообщении по ссылке. Это наводит на мысль, что где-то в природе существует отдельное описание этого принципа. Интересно было бы увидеть.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Это звучит резонно.
R>Я вижу, название принципа "nothing prohibits it" взято в кавычки в сообщении по ссылке. Это наводит на мысль, что где-то в природе существует отдельное описание этого принципа. Интересно было бы увидеть.
Я так рассуждаю:
для методов вектора (да и всех контейнеров) стандарт определяет несколько важных вещей:
(список с потолка)
это, по сути, декларация контракта.
Так вот для метода emplace_back (и многих других) не описано никаких ограничений. В том числе не написано "вот это не передавайте, иначе UB". Я это понимаю, как при "ненарушении" других правил языка (то есть где-то ранее не допущен UB) и при передаче некоторого валидного значения в emplace_back реализация STL соблюдет контракт и выполнит post-condtion и всю задекларированную логику.
Короче, если не нарушил pre-conditions, то все должно работать, даже если вставляемый элемент указывает внутрь контейнера (т.к. оговорок по этому поводу в стандарте нет).
Здравствуйте, uzhas, Вы писали:
U>Так вот для метода emplace_back (и многих других) не описано никаких ограничений. В том числе не написано "вот это не передавайте, иначе UB". Я это понимаю, как при "ненарушении" других правил языка (то есть где-то ранее не допущен UB) и при передаче некоторого валидного значения в emplace_back реализация STL соблюдет контракт и выполнит post-condtion и всю задекларированную логику. U>Короче, если не нарушил pre-conditions, то все должно работать, даже если вставляемый элемент указывает внутрь контейнера (т.к. оговорок по этому поводу в стандарте нет).
Да согласен. Но, с практической точки зрения, это означает, что разработчики стандартной бибилиотеки должны иметь в виду, что любой из фактических параметров, переданных в emplace_back, может ссылаться (прямо или косвенно) на какой-нибудь из существующих элементов контейнера и всегда поддрерживать время жизни всех элементов до окончания операции. Не будет ли это ударом по оптимизации?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, uzhas, Вы писали:
R>>Не будет ли это ударом по оптимизации?
U>Будет. Так же как и поддержка гарантий exception-safety. U>Ну а что поделать? (риторически вздохнул)
А как же тогда другой принцип — "не платить за то, что не используешь"?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>А как же тогда другой принцип — "не платить за то, что не используешь"?
ну это же лукавство, не правда ли?
возьми small string optimization в std::string — оно всем надо, думаешь? кому-то и эти 16 байт жалко тратить
или тот же exception safety
стараются сделать как можно гибче и тоньше, исходя из принципа, однако идеально не получится
да и не надо это. лучше иметь крепкий фундамент для своего софта, над ним как раз стандартизаторы и работают имхо
лазейка всегда есть : не хочешь платить — не используй std::vector
Здравствуйте, uzhas, Вы писали:
U>Так вот для метода emplace_back (и многих других) не описано никаких ограничений. В том числе не написано "вот это не передавайте, иначе UB". Я это понимаю, как при "ненарушении" других правил языка (то есть где-то ранее не допущен UB) и при передаче некоторого валидного значения в emplace_back реализация STL соблюдет контракт и выполнит post-condtion и всю задекларированную логику.
U>Короче, если не нарушил pre-conditions, то все должно работать, даже если вставляемый элемент указывает внутрь контейнера (т.к. оговорок по этому поводу в стандарте нет).
Это минорный дефект стандарта. Ведь знали же, что у программистов руки шаловливые, а забыли оговорить время жизни ссылки, передаваемой внутрь методов вектора.
Сказано явно лишь то, что во время выполнения этих методов все указатели и ссылки могут быть инвалидированы. Когда именно они инвалидируются, — не сказано.
Почему бы не предположить, что ссылка инвалидируется мгновенно при входе внутрь функции, просто потому, что реализация имеет право так сделать?
Ну и здравый смысл подсказывает, что конструктор с аргументом v.front() выполняется после reserve(size()+1), то есть, после инвалидации ссылок.
Конечно, мы можем написать код, который будет устойчив к таким проблемам
struct Robust {
Robust(int, int, int) { ..... }
Robust(const Robust&) {} // ничего не копирует
. . .
};
vector<Robust> v;
v.emplace_back(1,2,3);
Robust& r = v.front();
v.emplace_back(r);
// r инвалидировано, да и плевать - если мы его не будем использовать
но в общем случае — передача в конструктор копирования инвалидной ссылки приведёт к UB.
Здравствуйте, Кодт, Вы писали:
К>Нужно ли было всё это расписывать?
Хотя...
Есть способ корректной реализации.
1. Размещаем новый массив
2. Конструируем там новый элемент (все ссылки ещё валидны, все элементы ещё нетронуты)
3. Конструируем там move/copy все остальные элементы
4. Разрушаем элементы в старом массиве
5. Удаляем старый массив
Здравствуйте, Кодт, Вы писали:
К>Хотя... К>Есть способ корректной реализации.
К>1. Размещаем новый массив К>2. Конструируем там новый элемент (все ссылки ещё валидны, все элементы ещё нетронуты) К>3. Конструируем там move/copy все остальные элементы К>4. Разрушаем элементы в старом массиве К>5. Удаляем старый массив
Да и особого оверхеда не видно, вроде бы.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Да и особого оверхеда не видно, вроде бы.
кстати, глянул тут потроха студийные: при push_back делается reserve, но при исключении во время покладания ничего не откатывается, в том числе capacity
как следствие, формальное нарушение правила: в случае исключения push_back has no effect