Является ли `string.assign (string.data (), 5)` хорошо определенным или UB?

11

Коллега хотел написать это:

std::string_view strip_whitespace(std::string_view sv);

std::string line = "hello  ";
line = strip_whitespace(line);

Я сказал, что возвращение string_viewсделало меня априори непростым , и, кроме того, псевдоним здесь выглядел как UB для меня.

Я могу с уверенностью сказать, что line = strip_whitespace(line)в этом случае эквивалентно line = std::string_view(line.data(), 5). Я считаю, что вызов будет string::operator=(const T&) [with T=string_view], который определен, чтобы быть эквивалентным line.assign(const T&) [with T=string_view], который определен, чтобы быть эквивалентным line.assign(line.data(), 5), который определен для этого:

Preconditions: [s, s + n) is a valid range.
Effects: Replaces the string controlled by *this with a copy of the range [s, s + n).
Returns: *this.

Но это не говорит о том, что происходит, когда есть псевдоним.

Я задал этот вопрос вчера на cpplang Slack и получил смешанные ответы. Здесь вы найдете ответы на самые серьезные вопросы и / или эмпирический анализ реализаций реальных поставщиков библиотек.


Я написал тестовые случаи для string::assign, vector::assign, deque::assign, list::assign, и forward_list::assign.

  • Libc ++ заставляет все эти тестовые случаи работать.
  • Libstdc ++ заставляет их все работать, за исключением forward_list, что segfaults.
  • Я не знаю о библиотеке MSVC.

Segfault в libstdc ++ дает мне надежду, что это UB; но я также вижу, что и libc ++, и libstdc ++ будут прилагать большие усилия, чтобы сделать это, по крайней мере, в обычных случаях.

Quuxplusone
источник
Вы компилировали контрольные примеры с ASan и / или запускали их под Valgrind? Это исключило бы догадки о том, вызывает ли код нарушения прав доступа, хотя все еще может работать на практике, а не по определению.
Конрад Рудольф
1
«Если какая-либо функция-член или оператор basic_string выдает исключение, эта функция или оператор не оказывает никакого другого влияния на объект basic_string». - это заставляет выделение памяти происходить до того, как освободится существующее хранилище, так что в случае сбоя выделения генерируется исключение без изменения *this. Но я ничего не вижу, чтобы предотвратить повторное использование существующего хранилища, и в этом случае это становится неопределенным, поскольку семантика перезаписи хранилища не определена.
Сэм Варшавчик
2
Для упомянутых контейнеров последовательности это, безусловно, UB из-за нарушения предварительных условий assignв [tab: container.seq.req] .
грецкий орех

Ответы:

8

Если исключить пару исключений, которые не являются вашими, вызов неконстантной функции-члена (т. Е. assign) В строке делает недействительными [...] указатели [...] на ее элементы. Это нарушает предварительное условие о assignтом , что [s, s + n)это допустимый диапазон, так что это неопределенное поведение.

Обратите внимание, что string::operator=(string const&)есть язык, специально предназначенный для самостоятельного назначения.

ecatmur
источник
1
Так в чем же заключается недействительность и точка, в которой обязательное условие должно соблюдаться? Кажется, что ответ предполагает, что предварительное условие должно выполняться после вызова функции-члена.
грецкий орех
1
@walnut Я не являюсь юристом по языку (и не обладаю особым знанием C ++), но когда мы перевернем ваш сценарий, мы можем задать вопрос - может ли диапазон быть недействительным во время выполнения assign? Если да, то мы должны были бы установить конкретную точку внутри реализации assign, чтобы пометить, когда именно может произойти аннулирование, и я считаю, что это не то, что C ++ сделает. Хотя я могу ошибаться.
Fureeish
2
@Fureeish Я тоже не знаю, но вижу, например, выпуск 526 LWG , закрытый как « не дефект », который упоминает в своей рекомендации по закрытию, которая std::vector::insert(iterator pos, const T& value)должна работать, если valueнаходится в самом векторе, потому что стандарт не указывает, что он разрешено не работать, даже если эта ссылка может быть аннулирована вызовом.
грецкий орех
1
@walnut « требуется на работу , потому что стандарт не дает разрешения на это не работать. » - любить его . Ооочень ... стоит ли спрашивать что происходит на практике ? Требуется ли реализация, чтобы сделать копию аргумента в такой ситуации? Как вы могли бы реально реализовать это ..? Я слышал о стандарте, требующем, чтобы компиляторы делали невозможное - это один из таких случаев? Независимо от того, спасибо за комментарий!
Fureeish
1
@Fureeish На самом деле мой предыдущий (теперь удаленный) пример не проверял то, что я хотел проверить. Вот фиксированный пример, показывающий, что и libc ++, и libstdc ++ действительно копируют, прежде чем переходить на перераспределение по мере необходимости.
грецкий орех