10

小さな「ブロッキングキュー」クラスを作成しました。enqueueメンバー関数に渡される値の冗長コードを作成したことにイライラします。

正確に同じことを行う2つの関数を次に示します(右辺値がstd :: moveを使用して右辺値を実際のキューコレクションに移動することを除く)。ただし、左辺値と右辺値をそれぞれ処理します。

    void enqueue(const T& item)
    {
        std::unique_lock<std::mutex> lock(m);
        this->push(item);
        this->data_available = true;
        cv.notify_one();
    }

    void enqueue(T&& item)
    {
        std::unique_lock<std::mutex> lock(m);
        this->push(std::move(item));
        this->data_available = true;
        cv.notify_one();
    }

私の質問は、右辺値参照のサポートを失うことなく、これら2つの関数を組み合わせる方法があるかどうかです。

4

3 に答える 3

18

これは、完全に転送する必要がある典型的な例です。これを行うには、関数(これがメンバー関数の場合はメンバーテンプレート)をテンプレート化します。

template <class U>
void enqueue(U&& item)
{
    std::unique_lock<std::mutex> lock(m);
    this->push(std::forward<U>(item));
    this->data_available = true;
    cv.notify_one();
}

説明:左辺値Tをに渡すとenqueueUはに推論され、は左辺値として渡され、必要なコピー動作が得られます。に右辺値を渡すと、はに推論され、は右辺値として渡され、必要な移動動作が得られます。T&forwardTenqueueUTforward

これは、不要なコピーや移動を行わないという点で、「値渡し」アプローチよりも効率的です。「値渡し」アプローチの欠点は、関数が間違っていても何でも受け入れることです。カスケードエラーが発生する場合と発生しない場合がありますpush。これが懸念事項である場合は、enable_if enqueueインスタンス化する引数を制限できます。

コメントに基づいて更新

以下のコメントに基づいて、これは私が物事がどのように見えるかを理解しているものです:

#include <queue>
#include <mutex>
#include <condition_variable>

template <class T>
class Mine
    : public std::queue<T>
{
    std::mutex m;
    std::condition_variable cv;
    bool data_available = false;
public:

    template <class U>
    void
    enqueue(U&& item)
    {
        std::unique_lock<std::mutex> lock(m);
        this->push(std::forward<U>(item));
        this->data_available = true;
        cv.notify_one();
    }
};

int
main()
{
    Mine<int> q;
    q.enqueue(1);
}

これはすべて良いです。しかし、代わりにdoubleをエンキューしようとするとどうなりますか?

q.enqueue(1.0);

doubleは暗黙的にintに変換できるため、これは引き続き機能します。しかし、それを機能させたくない場合はどうなりますか?enqueue次に、次のように制限できます。

template <class U>
typename std::enable_if
<
    std::is_same<typename std::decay<U>::type, T>::value
>::type
enqueue(U&& item)
{
    std::unique_lock<std::mutex> lock(m);
    this->push(std::forward<U>(item));
    this->data_available = true;
    cv.notify_one();
}

今:

q.enqueue(1.0);

結果:

test.cpp:31:11: error: no matching member function for call to 'enqueue'
        q.enqueue(1.0);
        ~~^~~~~~~
test.cpp:16:13: note: candidate template ignored: disabled by 'enable_if' [with U = double]
            std::is_same<typename std::decay<U>::type, T>::value
            ^
1 error generated.

しかし、q.enqueue(1);それでも問題なく動作します。つまり、メンバーテンプレートを制限することは、設計上の決定を行う必要があります。何を受け入れUたいですか?enqueue正解も不正解もありません。これは工学的な判断です。そして、より適切かもしれない他のいくつかのテストが利用可能です(例えば、std :: is_convertible、std :: is_constructibleなど)。上で最初にプロトタイプを作成したように、アプリケーションの正しい答えはまったく制約がないかもしれません。

于 2013-02-03T00:36:26.507 に答える
7

enqueue(const&)私には、とenqueue(&&)はの特殊なケースであるように思われますenqueue_emplace。C ++ 11のような優れたコンテナには、これら3つの機能があり、最初の2つは3番目の特殊なケースです。

void enqueue(const T& item) { enqueue_emplace(item); }
void enqueue(T&& item)      { enqueue_emplace(std::move(item)); }

template <typename... Args>
void enqueue_emplace(Args&&... args)
{
    std::unique_lock<std::mutex> lock(m);
    this->emplace(std::forward<Args>(args)...); // queue already has emplace
    this->data_available = true;
    cv.notify_one();
}

これはシンプルですが効率的なソリューションであり、元のインターフェイスと同じように動作する必要があります。また、初期化子リストをキューに入れることができるため、テンプレートアプローチよりも優れています。


古い投稿:古き良き値渡しを行うだけです:

void enqueue(T item)
{
    std::unique_lock<std::mutex> lock(m);
    this->push(std::move(item));
    this->data_available = true;
    cv.notify_one();
}

enqueueそのパラメータを「所有」しており、キューに移動したいと考えています。値渡しは、これを言う正しい概念です。

それはちょうど1つのコピーと1つの動きをします。T残念ながら、最適化された移動コンストラクターがない場合、これは遅くなる可能性があります。このため、標準ライブラリのコンテナには常に2つのオーバーロードがあると思います。しかし一方で、優れたコンパイラはこれを最適化する可能性があります。

于 2013-02-03T00:06:00.170 に答える
2

見たことがありstd::forwardますか?あなたがあなたの関数に少しテンプレートを投げ込むならば、それはあなたが求めることをするかもしれません...

于 2013-02-03T00:33:05.553 に答える