Контейнер STL функционирует возвращаемые значения

При просмотре функций членства контейнеров STL нечетная мысль произошла со мной. Почему функциям не нравится std::vector<T>::push_back(T) не (дополнительно) возвращаемое значение имеют (итератор или даже ссылка на добавленный объект)? Я знаю std::string функции как insert и erase возвратите итераторы, но это по очевидным причинам. Я думал бы, что это будет часто сохранять вторую строку кода, которая часто следует за этими вызовами функции.

Я уверен, что у разработчиков C++ есть очень серьезное основание, просветите меня :)

ОБНОВЛЕНИЕ: я включаю реальный пример кода здесь, где он мог уменьшить разрядность кода:

if( m_token != "{" )
{
    m_targets.push_back( unique_ptr<Target>(new Dough(m_token)) );
    return new InnerState( *(m_targets.back()), this );
}

мог быть уменьшен до

if( m_token != "{" )
    return new InnerState( *(m_targets.push_back( unique_ptr<Target>(new Dough(m_token)) )), this );

Если я принимаю std::list::push_back возвращает ссылку на добавленный элемент. Код немного тяжел, но это главным образом (два набора круглых скобок) из-за unique_ptrконструктор и разыменование его. Возможно, для ясности версия без любых указателей:

if( m_token != "{" )
{
    m_targets.push_back( Dough(m_token) );
    return new InnerState( m_targets.back(), this );
}

по сравнению с.

if( m_token != "{" )
    return new InnerState( m_targets.push_back( Dough(m_token) ), this );
8
задан rubenvb 11 July 2010 в 15:19
поделиться

8 ответов

Возврат добавленного элемента или контейнера в функциях-членах контейнера невозможен безопасным способом. Контейнеры STL в основном обеспечивают "сильную гарантию". Возврат манипулируемого элемента или контейнера сделает невозможным обеспечение сильной гарантии (он обеспечит только "базовую гарантию"). Причина этого в том, что возврат чего-либо может вызвать копирующий конструктор, который может выдать исключение. Но функция уже вышла, поэтому она успешно выполнила свою основную задачу, но все равно выбросила исключение, что является нарушением сильной гарантии. Вы можете подумать: "Ну тогда давайте вернемся по ссылке!", и хотя это звучит как хорошее решение, оно тоже не совсем безопасно. Рассмотрим следующий пример:

MyClass bar = myvector.push_back(functionReturningMyClass()); // imagine push_back returns MyClass&

Все равно, если оператор copy-assignment бросает исключение, мы не знаем, удалось ли push_back выполнить или нет, тем самым косвенно нарушая strong-guarantee. Хотя это и не является прямым нарушением. Конечно, использование MyClass& bar = //... вместо него решит эту проблему, но это будет довольно неудобно, поскольку контейнер может попасть в неопределенное состояние только потому, что кто-то забыл &.

Совершенно аналогичные рассуждения стоят за тем, что std::stack::pop() не возвращает выгруженное значение. Вместо этого top() возвращает самое верхнее значение безопасным способом. После вызова top, даже когда бросается конструктор копирования или конструктор копирования-присвоения, вы все равно знаете, что стек неизменен.

EDIT:. Я считаю, что возвращение итератора для нового добавленного элемента должно быть совершенно безопасным, если конструктор копирования типа итератора обеспечивает гарантию отсутствия броска (а все известные мне конструкторы обеспечивают такую гарантию).

6
ответ дан 5 December 2019 в 12:07
поделиться

Интересный вопрос. Очевидным возвращаемым значением будет вектор (или что угодно), над которым выполняется операция, так что вы могли бы написать код типа:

if ( v.push_back(42).size() > n ) {
   // do something
}

Мне лично не нравится этот стиль, но я не могу придумать веской причины не поддерживать его.

5
ответ дан 5 December 2019 в 12:07
поделиться

Потому что есть .back (), который мгновенно вернет его для вас?

Концептуально дизайнеры C ++ не будут реализовывать в функции-члене ничего, что было бы сложно или невозможно реализовать в общедоступном интерфейсе. Вызвать .back () достаточно просто. Для итератора вы можете использовать (end - 1) или просто auto it = end; it -;

Комитет по стандартам делает возможным новый код и значительно упрощает код, который очень часто используется. Подобные вещи просто не входят в список дел.

3
ответ дан 5 December 2019 в 12:07
поделиться
v.insert(v.end(),x);

Было бы эквивалентно push_back с возвратом итератора. Почему сам push_back не возвращает итератор, мне непонятно.

2
ответ дан 5 December 2019 в 12:07
поделиться

Я думаю, это связано с концепцией возвращаемого значения: возвращаемое значение существует не для вашего удобства, а для концептуального результата «вычислений», по их мнению, push_back концептуально ни к чему не приводит.

0
ответ дан 5 December 2019 в 12:07
поделиться

Я не уверен, но думаю что одна из причин, по которой изменяющиеся члены std :: string возвращают итератор , заключается в том, что программист может получить неконстантный итератор для std :: string после операции изменения, не требующей второй «утечки».

Интерфейс std :: basic_string был разработан для поддержки шаблона под названием copy-on-write , что в основном означает, что любая операция изменения не влияет на исходные данные, но копировать.Например, если у вас есть строка «abcde» и вы заменили 'a' на 'z' , чтобы получить «zbcde» , данные для результирующей строки могут занимать в куче другое место, чем данные для исходной строки.

Если вы получаете неконстантный итератор std :: string , тогда реализация строки COW должна сделать копию (также называемую «утечкой оригинала»). В противном случае программа может изменить базовые данные (и нарушить инвариант только для чтения) на :

char& c0 = *str.begin();
c0 = 'z';

Но после операции изменения строки результирующий строковый объект уже имеет исключительное право собственности на данные, поэтому строковой реализации не требуется повторная утечка данных для создания неконстантного итератора.

std :: vector отличается, потому что он не поддерживает семантику копирования при записи.

Примечание: я получил термин утечка из реализации libstdc ++ std :: basic_string . Кроме того, «утечка данных» не означает, что реализация приводит к утечке памяти .

РЕДАКТИРОВАТЬ: Вот определение libstdc ++ для std :: basic_string :: begin () для справки:

iterator
begin()
{
    _M_leak();
    return iterator(_M_data());
}
0
ответ дан 5 December 2019 в 12:07
поделиться

Не уверен, что у них была веская причина, но эта функция уже достаточно медленная.

-2
ответ дан 5 December 2019 в 12:07
поделиться

Может быть, потому что он не был «нужен»?

erase () и insert () не имеют другого способа, кроме как вернуть итератор, чтобы продолжить цикл, в котором он был вызван.

Я не вижу веских причин для поддержки той же логики с push_back () .

Но, конечно, было бы замечательно сделать более загадочные выражения. (Я не вижу улучшений в вашем примере, похоже, это хороший способ замедлить ваших коллег при чтении кода ...)

0
ответ дан 5 December 2019 в 12:07
поделиться
Другие вопросы по тегам:

Похожие вопросы: