Чому std :: queue :: pop не повертає значення.?


123

Я пройшов через це сторінку, але не можу зрозуміти причину того ж. Там це згадується

"розумніше взагалі не повертати значення і вимагати від клієнтів використовувати 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 (), а потім призначає це результату. Отже, яка різниця, поп-функція могла зробити те саме.


Тому що реалізується таким чином (тобто void std::queue::pop();).
101010

На це питання відповіли, але як сторонне
позначення

1
Ваше посилання на документацію STL. Але ви запитуєте про стандартну бібліотеку C ++. Різні речі.
juanchopanza

5
"Але перевірка елемента з front()також вимагає, щоб цей елемент був скопійований у lvalue" - ні, це не так. frontповертає посилання, а не значення. Ви можете перевірити значення, на яке воно посилається, не копіюючи його.
Майк Сеймур

1
@KeminZhou описана вами модель вимагає копії. Може бути. Якщо ви хочете багаторазово споживати чергу, то так, ви повинні зробити копію перед тим, як звільнити замок у черзі. Однак якщо ви дбаєте лише про розділення входу та виходу, то вам не потрібен замок для огляду спереду. Ви можете зачекати, щоб заблокувати, поки ви не закінчите його споживання і вам потрібно зателефонувати pop(). Якщо ви використовуєте , std::queue<T, std::list<T>>то немає ніякого занепокоєння з приводу посилання , наданої від front()того анульовано push(). Але ви повинні знати свою схему використання та повинні документувати свої обмеження.
jwm

Відповіді:


105

Отже, яка різниця, поп-функція могла зробити те саме.

Це дійсно могло зробити те саме. Причина цього не полягала в тому, що поп, який повернув спливаючий елемент, небезпечний за наявності винятків (повернення за значенням і створення копії).

Розглянемо цей сценарій (з наївною / складеною поп-реалізацією, щоб проілюструвати мою думку):

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()).


хммм ... це має сенс
cbinder

37
Зверніть увагу на C ++ 11: якщо T має дешевий конструктор неісключення переміщення (що часто буває для виду об'єктів, що складаються в стеки), то повернення за значенням є ефективним та безпечним для винятків.
Роман L

9
@ DavidRodríguez-dribeas: Я не пропоную жодних змін, я висловив думку про те, що деякі зі згаданих недоліків стають меншими проблемами з C ++ 11.
Роман L

11
Але чому його називають pop? це досить контрінтуїтивно. Його можна було назвати drop, і тоді всім зрозуміло, що він не
стикає

12
@utnapistim: де-факто «поп» операція завжди полягала в тому, щоб взяти верхній елемент зі стека і повернути його. Коли я вперше натрапив на стеки STL, я, щонайменше, здивувався тому, що popнічого не повернути. Дивіться, наприклад, на wikipedia .
Кріс Луенго

34

Сторінка, яку ви пов’язали, відповідає на ваше запитання.

Процитуйте весь відповідний розділ:

Можна задатися питанням, чому pop () повертає недійсність, а не value_type. Тобто, чому потрібно використовувати front () та pop () для того, щоб перевірити та видалити елемент на передній частині черги, а не поєднувати два в одній функції члена? Насправді, для цієї конструкції є вагомі причини. Якщо pop () повернув передній елемент, він повинен був би повернутися за значенням, а не за посиланням: return за посиланням створив би звисаючий покажчик. Однак повернення за значенням неефективне: воно включає щонайменше один надмірний виклик конструктора копії. Оскільки поп () неможливо повернути значення таким чином, щоб воно було і ефективним, і правильним, для нього розумніше взагалі не повертати значення і вимагати від клієнтів використання фронту () для перевірки значення на передня частина черги.

C ++ розроблений з урахуванням ефективності, над кількістю рядків коду, який повинен написати програміст.


16
Можливо, але справжня причина полягає в тому, що неможливо реалізувати безпечну версію винятків (з сильною гарантією), версія popякої повертає значення.
Джеймс Канзе

5

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


4
Справжня причина - безпека винятків. Немає безпечного способу здійснити "транзакцію" зі стеком (або елемент залишається на стеку, або він повертається вам), якщо popповернення і акт повернення можуть призвести до винятку. Очевидно, доведеться видалити елемент, перш ніж він поверне його, і тоді, якщо щось кине, елемент може бути безповоротно загублений.
Джон

1
Чи все-таки це занепокоєння стосується with конструктора неісключених переміщень? (Звичайно 0 копій ефективніші, ніж два ходи, але це може відкрити двері для виключення безпечного, ефективного комбінованого фронту + попсу)
peppe

1
@peppe, ти можеш повернутись за значенням, якщо ти знаєш, що value_typeмає конструктор переміщення нерозбірливих даних, але інтерфейс черги буде відрізнятися залежно від типу об'єкта, який ви зберігаєте в ньому, що не буде корисним.
Джонатан Уейклі

1
@peppe: Це було б безпечно, але стек - це загальний клас бібліотеки, і чим більше він повинен вважати про тип елемента, тим менш корисним він є.
Джон

Я знаю, що STL не дозволив би цього зробити, але це означає, що можна створити власний клас черг з блекджеком та ^ W ^ W ^ W з цією функцією :)
peppe

3

З поточною реалізацією це дійсно:

int &result = myqueue.front();
std::cout << result;
myqueue.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 ++ не розвивається слабо ...)


0

Я думаю, що найкращим рішенням було б додати щось подібне

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

де значення отримає спливаюче значення.

Перевага полягає в тому, що він може бути реалізований за допомогою оператора присвоєння переміщення, в той час як використання фронту + поп зробить копію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.