std :: queue :: popの戻り値が返されないのはなぜですか?


123

私はこのページを通過しましたが、同じ理由を得ることができません。そこで言及されている

「値をまったく返さず、クライアントがfront()を使用してキューの先頭にある値を検査することを要求する方が賢明です」

しかし、front()から要素を検査するには、その要素を左辺値にコピーする必要もありました。たとえば、このコードセグメントでは

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

/ *ここで、結果に割り当てられるRHSに一時が作成されます。参照によって返される場合、ポップ操作後に結果が無効になります* /

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

5行目で、coutオブジェクトは最初にmyqueue.front()のコピーを作成し、それを結果に割り当てます。だから、違いは何ですか、ポップ機能は同じことをしたかもしれません。


このように実装されているため(つまりvoid std::queue::pop();)。
101010 2014

質問への回答はありましたが、補足として:戻るポップが本当に必要な場合は、無料の関数で簡単に実装できます:ideone.com/lnUzf6
eerorika

1
あなたのリンクはSTLドキュメントへのリンクです。しかし、C ++標準ライブラリについて質問しています。異なるもの。
juanchopanza 2014

5
「しかし、要素を検査するには、front()その要素も左辺値にコピーする必要がありました」-いいえ、そうではありません。front値ではなく参照を返します。コピーせずに、それが参照する値を検査できます。
マイクシーモア

1
@KeminZhou記述したモデルにはコピーが必要です。多分。キューの消費を多重化する場合は、はい、キューのロックを解放する前にコピーを作成する必要があります。ただし、入力と出力の分離のみに関心ある場合は、正面を検査するためのロックは必要ありません。使用が終了してを呼び出す必要があるまで、ロックを待つことができますpop()。を使用する場合std::queue<T, std::list<T>>、提供さfront()れる参照がによって無効化されることについての心配はありませんpush()。ただし、使用パターンを把握し、制約を文書化する必要があります。
jwm 2018

回答:


105

だから、違いは何ですか、ポップ機能は同じことをしたかもしれません。

それは確かに同じことをしたかもしれません。そうしなかった理由は、ポップされた要素を返したポップは、例外が存在すると安全ではないためです(値で返す必要があるため、コピーを作成します)。

このシナリオを考えてみてください(私のポイントを説明するために、素朴で構成された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私の単純な実装では)既に変更されており、要素はキューから削除されます(返されません)。すべての意図と目的(クライアントコードで例外をキャッチする方法に関係なく)の場合、キューの先頭にある要素は失われます。

この実装は、ポップされた値が必要ない場合(つまり、誰も使用しない要素のコピーを作成する場合)にも非効率的です。

これは、2つの別々の操作(void popおよびconst T& front())で安全かつ効率的に実装できます。



37
C ++ 11の注:Tに安価なnoexcept move-constructor(スタックに置かれた種類のオブジェクトの場合がよくある)がある場合、値による戻りは効率的で例外に対して安全です。
Roman L

9
@DavidRodríguez-dribeas:変更を提案するつもりはありません。私の指摘は、前述の欠点の一部がC ++ 11での問題の少ないものになるということでした。
Roman L

11
しかし、なぜそれが呼ばれるのpopですか?それはかなり直感に反しています。名前をdrop
付ける

12
@utnapistim:事実上の「ポップ」操作は、常にスタックから最上位の要素を取得して返すことです。最初にSTLスタックに遭遇したとき、控えめに言っても、pop何も返さないことに驚きました。ウィキペディアの例を参照してください。
クリスLuengo

34

リンクしたページが質問に回答します。

関連するセクション全体を引用するには:

pop()がvalue_typeではなくvoidを返すのはなぜでしょうか。つまり、単一のメンバー関数で2つを組み合わせるのではなく、なぜキューの前の要素を調べて削除するために、front()とpop()を使用する必要があるのですか?実際、この設計には正当な理由があります。pop()がフロント要素を返した場合、参照ではなく値で返す必要があります。参照で戻ると、ぶら下がりポインタが作成されます。ただし、値による戻りは非効率的です。少なくとも1つの冗長なコピーコンストラクター呼び出しが必要です。pop()が効率的で正しい方法で値を返すことは不可能であるため、値をまったく返さず、クライアントがfront()を使用して値を検査することを要求する方が賢明ですキューの前。

C ++は、プログラマーが書かなければならないコードの行数よりも効率性を考慮して設計されています。


16
おそらく、しかし、本当の理由はpop、値を返すバージョンに対して(強力な保証付きの)例外安全なバージョンを実装することが不可能であることです。
James Kanze、2014

5

popは、データ構造から削除されているため、削除された値への参照を返すことができないので、参照は何を参照する必要がありますか?値で返すこともできますが、popの結果がどこにも保存されていない場合はどうなりますか?次に、値を不必要にコピーして時間を無駄にします。


4
本当の理由は例外安全です。pop戻り値と戻り値の動作によって例外が発生する可能性がある場合、スタックとの「トランザクション」(要素がスタック上にとどまるか、要素に戻される)を行う安全な方法はありません。要素を返す前に要素を削除する必要があることは明らかであり、何かがスローされた場合、要素は完全に失われる可能性があります。
2014

1
この懸念は、no nocept moveコンストラクターfiにも適用されますか?(もちろん、2つのコピーよりも0コピーの方が効率的ですが、これは例外的に安全で効率的な結合されたフロント+ポップの扉を開く可能性があります)
peppe

1
@ peppe、value_typenothrow moveコンストラクターがあることがわかっている場合は値で返すことができますが、キューインターフェイスは格納するオブジェクトのタイプによって異なるため、役に立ちません。
ジョナサンウェイクリー2014

1
@peppe:それは安全ですが、スタックは一般的なライブラリクラスであるため、要素の型について想定する必要が多ければ多いほど、その有用性は低くなります。
2014

私は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 ++言語は軽く進化していません...)


0

私は最良の解決策は次のようなものを追加することだと思います

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

値はポップされた値を受け取ります。

利点は、移動代入演算子を使用して実装できる一方で、front + popを使用するとコピーが作成されることです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.