При просмотре функций членства контейнеров 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 );
Возврат добавленного элемента или контейнера в функциях-членах контейнера невозможен безопасным способом. Контейнеры 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:. Я считаю, что возвращение итератора для нового добавленного элемента должно быть совершенно безопасным, если конструктор копирования типа итератора обеспечивает гарантию отсутствия броска (а все известные мне конструкторы обеспечивают такую гарантию).
Интересный вопрос. Очевидным возвращаемым значением будет вектор (или что угодно), над которым выполняется операция, так что вы могли бы написать код типа:
if ( v.push_back(42).size() > n ) {
// do something
}
Мне лично не нравится этот стиль, но я не могу придумать веской причины не поддерживать его.
Потому что есть .back (), который мгновенно вернет его для вас?
Концептуально дизайнеры C ++ не будут реализовывать в функции-члене ничего, что было бы сложно или невозможно реализовать в общедоступном интерфейсе. Вызвать .back () достаточно просто. Для итератора вы можете использовать (end - 1) или просто auto it = end; it -;
Комитет по стандартам делает возможным новый код и значительно упрощает код, который очень часто используется. Подобные вещи просто не входят в список дел.
v.insert(v.end(),x);
Было бы эквивалентно push_back с возвратом итератора. Почему сам push_back не возвращает итератор, мне непонятно.
Я думаю, это связано с концепцией возвращаемого значения: возвращаемое значение существует не для вашего удобства, а для концептуального результата «вычислений», по их мнению, push_back концептуально ни к чему не приводит.
Я не уверен, но думаю что одна из причин, по которой изменяющиеся члены 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
для справки:
iterator
begin()
{
_M_leak();
return iterator(_M_data());
}
Не уверен, что у них была веская причина, но эта функция уже достаточно медленная.
Может быть, потому что он не был «нужен»?
erase ()
и insert ()
не имеют другого способа, кроме как вернуть итератор, чтобы продолжить цикл, в котором он был вызван.
Я не вижу веских причин для поддержки той же логики с push_back ()
.
Но, конечно, было бы замечательно сделать более загадочные выражения. (Я не вижу улучшений в вашем примере, похоже, это хороший способ замедлить ваших коллег при чтении кода ...)