Почему std :: queue :: pop не возвращает значение?

123

Я просмотрел эту страницу, но не могу понять причину того же. Там упоминается, что

"для него более разумно вообще не возвращать никакого значения и требовать от клиентов использования front () для проверки значения в начале очереди"

Но проверка элемента из front () также требует, чтобы этот элемент был скопирован в lvalue. Например, в этом сегменте кода

std::queue<int> myqueue;
int myint;
int result;
std::cin >> myint;
myqueue.push (myint);

/ * здесь временно будет создано на RHS, которое будет назначено результату, и в случае возврата по ссылке результат будет недействительным после операции pop * /

result = myqueue.front();  //result.
std::cout << ' ' << result;
myqueue.pop();

в пятой строке объект cout сначала создает копию myqueue.front (), а затем присваивает ее результату. Итак, в чем разница, функция pop могла бы сделать то же самое.

cbinder
источник
Потому что реализовано так (т.е. void std::queue::pop();).
101010
На вопрос был дан ответ, но в качестве примечания: если вы действительно хотите, чтобы всплывающее окно возвращалось, его можно легко реализовать с помощью бесплатной функции: ideone.com/lnUzf6
eerorika
1
Ваша ссылка ведет к документации STL. Но вы спрашиваете о стандартной библиотеке C ++. Разные вещи.
juanchopanza
5
«Но проверка элемента из front()также требует, чтобы этот элемент был скопирован в lvalue» - нет, это не так. frontвозвращает ссылку, а не значение. Вы можете проверить значение, к которому оно относится, не копируя его.
Майк Сеймур
1
@KeminZhou для описываемой вами модели требуется копия. Может быть. Если вы хотите мультиплексировать потребление очереди, то да, вы должны сделать копию, прежде чем снимать блокировку очереди. Однако, если вы заботитесь только о разделении ввода и вывода, вам не нужна блокировка для проверки передней части. Вы можете подождать, пока блокировка не закончится, и вам нужно будет позвонить pop(). Если вы используете, std::queue<T, std::list<T>>то не стоит беспокоиться о том, что предоставленная ссылка front()будет аннулирована файлом push(). Но вы должны знать свой шаблон использования и задокументировать свои ограничения.
jwm

Ответы:

105

Итак, в чем разница, функция pop могла бы сделать то же самое.

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

Рассмотрим этот сценарий (с наивной / выдуманной реализацией pop, чтобы проиллюстрировать мою точку зрения):

template<class T>
class queue {
    T* elements;
    std::size_t top_position;
    // stuff here
    T pop()
    {
        auto x = elements[top_position];
        // TODO: call destructor for elements[top_position] here
        --top_position;  // alter queue state here
        return x;        // calls T(const T&) which may throw
    }

Если конструктор копирования T выдает при возврате, вы уже изменили состояние очереди ( top_positionв моей наивной реализации), и элемент удаляется из очереди (и не возвращается). Для всех намерений и целей (независимо от того, как вы поймаете исключение в клиентском коде) элемент в верхней части очереди теряется.

Эта реализация также неэффективна в том случае, когда вам не нужно извлекаемое значение (т.е. создается копия элемента, которую никто не будет использовать).

Это можно реализовать безопасно и эффективно с помощью двух отдельных операций ( void popи const T& front()).

utnapistim
источник
хммм ... это имеет смысл
cbinder
37
Примечание C ++ 11: если у T есть дешевый конструктор перемещения noexcept (что часто бывает с объектами, помещенными в стеки), то возврат по значению эффективен и безопасен в отношении исключений.
Roman L
9
@ DavidRodríguez-dribeas: Я не предлагаю никаких изменений, я хотел сказать, что некоторые из упомянутых недостатков стали менее серьезной проблемой для C ++ 11.
Roman L
11
Но почему это называется pop? это довольно противоречит интуиции. Ему можно было бы дать имя drop, и тогда для всех будет ясно, что он не
выталкивает
12
@utnapistim: де-факто операция "pop" всегда заключалась в том, чтобы взять верхний элемент из стека и вернуть его. Когда я впервые наткнулся на стеки STL, я был, мягко говоря, удивлен, что popничего не вернул. См. Например в википедии .
Cris Luengo
34

Страница, на которую вы ссылаетесь, отвечает на ваш вопрос.

Чтобы процитировать весь соответствующий раздел:

Можно задаться вопросом, почему pop () возвращает void вместо value_type. То есть, почему нужно использовать front () и pop () для проверки и удаления элемента в начале очереди, вместо того, чтобы объединять их в одной функции-члене? На самом деле, для такого дизайна есть веская причина. Если pop () вернет передний элемент, он должен будет вернуться по значению, а не по ссылке: возврат по ссылке создаст висящий указатель. Однако возврат по значению неэффективен: он включает в себя как минимум один вызов конструктора избыточной копии. Поскольку pop () не может возвращать значение таким образом, чтобы быть одновременно эффективным и правильным, для него более разумно вообще не возвращать никакого значения и требовать от клиентов использования front () для проверки значения в начало очереди.

C ++ разработан с учетом эффективности, с учетом количества строк кода, которые программист должен написать.

BPF2010
источник
16
Возможно, но настоящая причина в том, что невозможно реализовать безопасную в отношении исключений версию (с сильной гарантией) для версии, popкоторая возвращает значение.
Джеймс Канце
5

pop не может вернуть ссылку на значение, которое было удалено, поскольку оно удаляется из структуры данных, поэтому на что должна ссылаться ссылка? Он может возвращаться по значению, но что, если результат pop нигде не сохраняется? Тогда время тратится на ненужное копирование значения.

Нил Кирк
источник
4
Настоящая причина - безопасность исключений. Не существует безопасного способа иметь «транзакцию» со стеком (либо элемент остается в стеке, либо он возвращается вам), если popвозврат и действие возврата может привести к исключению. Очевидно, ему придется удалить элемент, прежде чем он вернет его, а затем, если что-то выбрасывает, элемент может быть безвозвратно утерян.
Джон
1
Сохраняется ли эта проблема с конструктором перемещения без исключения? (Конечно, 0 копий более эффективны, чем два хода, но это может открыть дверь для безопасного, действенного комбинированного фронта + поп)
peppe
1
@peppe, вы можете вернуться по значению, если знаете, что у value_typeнего есть конструктор перемещения nothrow, но тогда интерфейс очереди будет отличаться в зависимости от того, какой тип объекта вы храните в нем, что не поможет.
Джонатан Уэйкли
1
@peppe: Это было бы безопасно, но стек - это общий библиотечный класс, и поэтому чем больше он должен предполагать о типе элемента, тем менее он полезен.
Джон
Я знаю, что STL этого не допустит, но это означает, что можно создать свой собственный класс очереди с помощью блэкджека и ^ W ^ W ^ W с этой функцией :)
Peppe
3

В текущей реализации это действительно так:

int &result = myqueue.front();
std::cout << result;
myqueue.pop();

Если pop вернет ссылку, например:

value_type& pop();

Тогда может произойти сбой следующего кода, поскольку ссылка больше не действительна:

int &result = myqueue.pop();
std::cout << result;

С другой стороны, если он вернет значение напрямую:

value_type pop();

Затем вам нужно будет сделать копию, чтобы этот код заработал, что менее эффективно:

int result = myqueue.pop();
std::cout << result;
Ян Рюэгг
источник
1

Начиная с C ++ 11, можно было бы архивировать желаемое поведение, используя семантику перемещения. Нравится pop_and_move. Таким образом, конструктор копирования не будет вызываться, а производительность будет зависеть только от конструктора перемещения.

Вячеслав Пуценко
источник
2
Нет, семантика перемещения волшебным образом не делает popисключения безопасными.
LF
0

Вы можете сделать это полностью:

std::cout << ' ' << myqueue.front();

Или, если вам нужно значение переменной, используйте ссылку:

const auto &result = myqueue.front();
if (result > whatever) do_whatever();
std::cout << ' ' << result;

Рядом с этим: формулировка «более разумный» - это субъективная форма «мы изучили шаблоны использования и обнаружили большую потребность в разделении». (Будьте уверены: язык C ++ развивается нелегко ...)

xtofl
источник
0

Думаю, лучшим решением было бы добавить что-нибудь вроде

std::queue::pop_and_store(value_type& value);

где value получит всплывающее значение.

Преимущество заключается в том, что это можно реализовать с помощью оператора присваивания перемещения, а использование front + pop сделает копию.

Массе Николя
источник