5

私はcppreferenceでstd::threadのドキュメントを読んでいて(常に100%正確であるとは限りません)std::thread、「pointer-to-data-member」(「pointer-to-」ではない)が渡されたときの動作に関する次の定義に気づきました。 member-function ")を最初の引数(f)として、必要なクラスのオブジェクトを2番目の引数として(t1thread-local-storageにコピーした後):

N == 1で、fがクラスのメンバーデータオブジェクトへのポインタである場合、そのオブジェクトにアクセスします。オブジェクトの値は無視されます。事実上、次のコードが実行されます。t1。* fの場合、t1のタイプはT、Tへの参照、またはTから派生したタイプへの参照(* t1)。*fのいずれかです。

今、私はこのように使用するつもりはありませんstd::threadが、私はこの定義に混乱しています。どうやら、発生する唯一のことは、データメンバーがアクセスされ、値が無視されることです。これは、観察可能な副作用をまったく持たないようです。つまり、(私が知る限り)それはノーオペレーション。(私は明らかな何かを見逃しているかもしれません...?)

最初は、これはミスプリントである可能性があり、データメンバーにアクセスしてから呼び出されることを意味していました(関数でなくても呼び出し可能なオブジェクトである可能性があるため)が、GCCで次のコードを使用してテストしました-4.7そして実際に呼び出しはありません:

#include <iostream>
#include <thread>

struct S 
{
    void f() {
        std::cout << "Calling f()" << std::endl;
    }

    struct {
        void operator()() {
            std::cout << "Calling g()" << std::endl;
        }
    } g;
};

int main(int, char**)
{
    S s;
    s.f(); // prints "Calling f()"
    s.g(); // prints "Calling g()"

    std::cout << "----" << std::endl;

    auto x = &S::f; // ptr-to-mem-func
    auto y = &S::g; // ptr-to-data-mem

    (s.*x)(); // prints "Calling f()"
    (s.*y)(); // prints "Calling g()"

    std::cout << "----" << std::endl;

    std::thread t(x, &s);
    t.join();
    // "Calling f()" printed by now
    std::thread u(y, &s);
    u.join();
    // "Calling g()" not printed

    return 0;
}

何も達成していないように見えるこの定義の目的はありますか?代わりに、「pointer-to-data-member-callable」の受け渡しを「pointer-to-member-function」のように動作させ、「pointer-to-data-member-noncallable」の受け渡しをエラーにしないのはなぜですか?実際、「pointer-to-data-member-callable」を呼び出すことは、他のコンテキストで「pointer-to-member-function」として呼び出すことと同等の構文を持っているため、これが実装する最も簡単な方法のようです(テンプレートの特殊化とSFINAEルールの迷信に、それらを同等に扱うことを困難にする何かがない限り...?)

これは実際のコードに必要なものではありませんが、この定義が存在するという事実は、私が基本的な何かを見逃しているのではないかと疑っています。

4

1 に答える 1

3

std::bindこれは、C ++ 11標準がスレッドの開始方法だけでなく、その方法と動作も定義する汎用バインディング機能のためですstd::function

実際、C ++11標準のパラグラフ30.3.1.2/3は、クラスの可変個引数コンストラクターについて指定していますstd::thread

template <class F, class ...Args> explicit thread(F&& f, Args&&... args);

効果:スレッド型のオブジェクトを構築します。新しい実行スレッドは、構築スレッドで評価されるINVOKE (DECAY_- COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)呼び出しとともに 実行されます。DECAY_COPYこの呼び出しからの戻り値はすべて無視されます。[...]

何をするかを無視するDECAY_COPYと(質問とは関係ありません)、これは段落20.8.2がINVOKE疑似関数を定義する方法です。

INVOKE(f、t1、t2、...、tN)を次のように定義します。

—(t1。* f)(t2、...、tN)fがクラスTのメンバー関数へのポインターであり、t1がタイプTのオブジェクト、またはタイプTのオブジェクトへの参照、またはTから派生したタイプのオブジェクト。

—((* t1)。* f)(t2、...、tN)fがクラスTのメンバー関数へのポインターであり、t1が前の項目で説明したタイプの1つではない場合。

—t1。*f(N == 1であり、fがクラスTのメンバーデータへのポインタであり、t1がタイプTのオブジェクトまたはタイプTのオブジェクトへの参照またはから派生したタイプのオブジェクトへの参照である場合) T;

(* t1)。* f N == 1であり、fがクラスTのメンバー・データへのポインターであり、t1が前の項目で説明したタイプの1つではない場合。

—他のすべての場合はf(t1、t2、...、tN)。

今、質問は次のようになります:

C ++ 11標準がそのように機能を定義するのはなぜINVOKEですか?

そして答えはここにあります。

于 2013-02-26T02:37:06.007 に答える