Здравствуйте, Caracrist, Вы писали:
C>C>std::vector<T> v;
C>
C>is subj. UB?
1. Если вектор пуст, то UB, т.к. вызов v[0] в случае пустоты имеет право убица апстену.
2. Если вектор не пуст, то сначала возьмём константную ссылку на v[0] (адрес памяти), потом вызовем emplace_back(), что может привести к переезду памяти с освобождением старого куска, куда показывает взятый указатель на v[0] (ссылка) и тогда при последующем старте конструирования нового emplaced-объекта получим тоже UB по причине чтения из ссылки (указателя) уже ведущей вникуда.
Короче, да, это UB.
Всякие там советы "не делай так" — тупые, ибо не содержат аргументов.
rg45:
MW>>О, нашёл:
R>R>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.
R>Это звучит резонно.
R>Я вижу, название принципа "nothing prohibits it" взято в кавычки в сообщении по ссылке. Это наводит на мысль, что где-то в природе существует отдельное описание этого принципа. Интересно было бы увидеть.
Это просто следствие непонимания некоторыми стандартизаторами принципов составления строгих формальных описаний. Неспособность родить ясное и непротиворечивое формальное описание приводит к тому, что под фактическим набором правил языка стандартизаторами подразумется не собственно то, что сказано в стандарте прямым текстом, а некая интерпретация (порой довольно неочевидная) стандарта этими самыми стандартизаторами.
Формально поведение можно считать определённым тогда и только тогда, когда существует набор правил, описывающий какое именно поведение дожно иметь место. Единственное допустимое применение принципа "nothing prohibits it" — это установление факта, что в рассматриваемом случае поведение действительно определено неким существующим набором правил, поскольку данный случай соответствует общим условиям этих правил и не соответвует каким-либо специальным условиям (исключительным случаям), которые могли бы сделать данный набор правил неприменимым к данному случаю.
Что касается v.emplace_back(v[0]), то стандарт не определяет порядок noexcept инициализации нового объекта и инвалидации v[0] в случае реаллокации. А это значит, что теоретически допустима conforming реализация, которая бы делала v[0] невалидным до конструирования вставляемого объекта, и тогда "nothing prohibits it" звучит довольно смешно, т.к. наличие undefined behavior следует собственно из описания (довольно кривого, кстати) семантики операции вставки.
Если инициализация вставляемого объекта потенциально может бросать исключение, то у реализации не остаётся выбора, кроме как сначала пытаться его конструировать, а потом уже заниматься удалением исходной последовательности, потому что в случае выброса исключения стандарт требует сохранения прежнего состояния вектора. Если реализация использует один и тот же код для потенциально бросающей и небросающей инициализации, то, скорее всего, v.emplace_back(v[0]) будет работать "корректно".
Подытожим:
1. Для непустого вектора v при v.size() == v.capacity() вычисление v.emplace_back(v[0]) формально может привести к undefined behavior за счёт инвалидации v[0].
2. Стандатизаторы одновременно не признают такую возможность и не признают наличие дефекта в стандарте, потому что они упоротые. Когда правила вменяемо сформулированы, споров вокруг их правильной трактовки не должно возникать в принципе. Если бы в сообществе математиков теоремы формулировались и доказывались с таким же отношением к формальной логике, мы бы сейчас не имели ничего сложнее доказательства теоремы Пифагора (да и там бы, наверное, не обошлось без ошибок и дефект-репортов).
3. Существует реализация std::vector (MSVC), для которой проблема "некорректного" вычисления v.emplace_back(v[0]) действительно имеет место.
4. Вроде как, в новой MSVC реализацию уже пофиксили:
https://blogs.msdn.microsoft.com/vcblog/2017/02/06/stl-fixes-in-vs-2017-rtm/
Здравствуйте, N. I., Вы писали:
NI>Это просто следствие непонимания некоторыми стандартизаторами принципов составления строгих формальных описаний.
Со строгостью и понятностью в стандарте плоховато. Могли бы в каждой новой версии вводить хотя бы "очевидные" для стандартизаторов (но неочевидные остальным) пояснения\уточнения. То же разыменование нулевого указателя (вспомним недавнее длинное обсуждение) вызывает массу вопросов когда это UB, а когда — нет.
NI>Что касается v.emplace_back(v[0]), то стандарт не определяет порядок noexcept инициализации нового объекта и инвалидации v[0] в случае реаллокации. А это значит, что теоретически допустима conforming реализация, которая бы делала v[0] невалидным до конструирования вставляемого объекта
Порядок не обязательно определять. Есть требование к std::vector (и emplace_back и реаллокация под его контролем) выполнить контракт (в каком порядке и что он будет делать — его забота). Не вижу тут возможности для conforming реализации допустить UB.
uzhas:
U>Есть требование к std::vector (и emplace_back и реаллокация под его контролем) выполнить контракт (в каком порядке и что он будет делать — его забота).
Что именно emplace_back должна выполнить? Operational semantics у emplace_back следующая:
Appends an object of type T constructed with std::forward<Args>(args)...
Вот, есть у нас, например, такой код:
#include <vector>
int main()
{
std::vector<int> v;
int i;
v.emplace_back(i);
}
Попытка создать
an object of type int constructed with std::forward<int &>(i) не cможет обойтись без вычисления lvalue-to-rvalue conversion над std::forward<int &>(i), а в соответствии с правилами С++17 [conv.lval]/3 и [dcl.init]/12 такое вычисление имеет своим результатом indeterminate value и ведёт к undefined behavior. Таким образом, в данном случае прямое выполнение operational semantics метода emplace_back подразумевает возникновение undefined behavior. Нарушили мы при этом какие-то коряво сформулированные куцые "предусловия" emplace_back или нет, тут особой роли не играет, — появление undefined behavior неминуемо по-любому.
Здравствуйте, uzhas, Вы писали:
U>Порядок не обязательно определять.
Всё-таки порядок надо определять.
Смотрим на такой пример, где "косвенность немного увеличена" :
https://ideone.com/A8kdp7
| | Скрытый текст |
| | #include <iostream>
#include <string>
#include <vector>
using namespace std;
vector<pair<int, string>> v = {{1, "oops"}}; // 1 element inside
int main() {
auto& e = v[0];
v.emplace_back(e.first, e.second);
return 0;
}
|
| | |