Re[9]: v.emplace_back(v[0])
От: Кодт Россия  
Дата: 09.10.17 16:44
Оценка:
Здравствуйте, rg45, Вы писали:

К>>Есть способ корректной реализации.

R>Да и особого оверхеда не видно, вроде бы.

Это я посмотрел, как работает gcc. И, казалось бы, всё логично.
Перекуём баги на фичи!
Re: Никто так и не объяснил по делу.
От: pkl  
Дата: 09.10.17 23:16
Оценка: -2
Здравствуйте, 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.
Всякие там советы "не делай так" — тупые, ибо не содержат аргументов.
Отредактировано 09.10.2017 23:19 pkl . Предыдущая версия . Еще …
Отредактировано 09.10.2017 23:19 pkl . Предыдущая версия .
Re[5]: v.emplace_back(v[0])
От: N. I.  
Дата: 10.10.17 19:36
Оценка: 11 (2) +2
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/
Отредактировано 10.10.2017 19:39 N. I. . Предыдущая версия .
Re[6]: v.emplace_back(v[0])
От: uzhas Ниоткуда  
Дата: 11.10.17 09:04
Оценка:
Здравствуйте, N. I., Вы писали:

NI>Это просто следствие непонимания некоторыми стандартизаторами принципов составления строгих формальных описаний.


Со строгостью и понятностью в стандарте плоховато. Могли бы в каждой новой версии вводить хотя бы "очевидные" для стандартизаторов (но неочевидные остальным) пояснения\уточнения. То же разыменование нулевого указателя (вспомним недавнее длинное обсуждение) вызывает массу вопросов когда это UB, а когда — нет.

NI>Что касается v.emplace_back(v[0]), то стандарт не определяет порядок noexcept инициализации нового объекта и инвалидации v[0] в случае реаллокации. А это значит, что теоретически допустима conforming реализация, которая бы делала v[0] невалидным до конструирования вставляемого объекта


Порядок не обязательно определять. Есть требование к std::vector (и emplace_back и реаллокация под его контролем) выполнить контракт (в каком порядке и что он будет делать — его забота). Не вижу тут возможности для conforming реализации допустить UB.
Re[7]: v.emplace_back(v[0])
От: N. I.  
Дата: 11.10.17 15:57
Оценка: +1
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 неминуемо по-любому.
Re[7]: v.emplace_back(v[0])
От: uzhas Ниоткуда  
Дата: 12.10.17 08:10
Оценка:
Здравствуйте, 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;
}
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.