2013-02-26 13 views
5

Przeczytałem przez std::thread documentation at cppreference (nie zawsze w 100% dokładny, wiem) i zauważyłem następującą definicję zachowania std::thread po podaniu "wskaźnika do danych-członka" (nie „wskaźnik do członków funkcji”) jako pierwszego argumentu (f) i obiektem wymaganej klasy jako drugi argument (t1 po skopiowaniu nitki-lokalny przechowywania):std :: wątek ze wskaźnikiem do elementu danych

jeśli N == 1 i f jest wskaźnikiem do obiektu danych elementu klasy, to jest on dostępny. Wartość obiektu jest ignorowana. W efekcie wykonywany jest następujący kod: t1. * F if, a typ t1 to T, odniesienie do T lub odniesienie do typu otrzymanego z T. (* t1). * F w przeciwnym wypadku.

Teraz nie planuję używać w ten sposób std::thread, ale jestem oszołomiony tą definicją. Wydaje się, że jedyną rzeczą, która się dzieje jest to, że członek danych jest dostępny, a wartość zignorowana, co nie wydaje się, że może mieć jakiekolwiek obserwowalne efekty uboczne, co oznacza (o ile mogę powiedzieć), że równie dobrze może być no-op. (Może brakuje mi czegoś oczywistego ...?)

Początkowo myślałem, że to może być błąd drukarski, który oznaczał, że członek danych jest dostępny, a następnie wywoływany (ponieważ może to być obiekt do wywołania, nawet jeśli to nie jest funkcja), ale ja testowałem to z następującego kodu w gcc-4.7 i rzeczywiście nie ma połączenia:

#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; 
} 

Czy istnieje jakiś cel do tej definicji, która nie wydaje się niczego osiągnąć? Dlaczego zamiast tego sprawić, by przekazanie "wskaźnika do elementu danych do pobrania" działało jak funkcja "wskazywania na członka" i sprawić, że przekazanie "braku wskaźnika do danych-członka nieopublikowanego" było błędem? W rzeczywistości wydaje się, że byłby to najłatwiejszy sposób implementacji, ponieważ wywoływanie "wskaźnika do danych-elementu-wywoływalnego" ma równoważną składnię do wywoływania jako "wskaźnik-do-funkcji-członka" w innych kontekstach (chyba że jest coś w różnorodności szablonowej specjalizacji i reguł SFINAE, co sprawia, że ​​trudno jest je traktować równoważnie ...?)

To nie jest coś, czego potrzebuję do rzeczywistego kodu, ale fakt, że ta definicja istnieje, pozwala mi podejrzewać, że Brakuje mi czegoś fundamentalnego, co mnie martwi ... czy ktoś może mnie o tym poinformować?

+0

+1 To sformułowanie pochodzi od definicji "INVOKE", która jest podana w 20.8.2 normy. Jest pytanie z takim samym zamiarem, jak twoje, szczególnie odnośnie tego sformułowania w definicji "INVOKE": http://stackoverflow.com/questions/12638393/why-does-invoke-facility-in-the-c11-standard-refer -to-danych-członków – jogojapan

Odpowiedz

3

Dzieje się tak ze względu na ogólny obiekt wiążący, w którym standard C++ 11 definiuje nie tylko sposób uruchamiania wątku, ale także sposób działania.

W rzeczywistości § 30.3.1.2/3 z C++ 11 Określono o zmiennej liczbie argumentów konstruktora klasy std::thread:

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

Effects: Konstrukty przedmiot gwincie . Nowy wątek wykonywania wykonuje z wywołaniami do DECAY_COPY ocenianych w wątku konstrukcyjnym. Każda wartość zwracana z tego wywołania jest ignorowana. [...]

Zignorowanie tego, co robi DECAY_COPY (nie ma to znaczenia dla pytania), tak działa § 20.8.2 określa INVOKE pseudo funkcji:

zdefiniować powołaniem się (K, T1, T2, ..., Tn) w następujący sposób: - (. T1 * F)

(t2, .. ., tN), gdy f jest wskaźnikiem do funkcji składowej klasy T, a t1 jest obiektem typu T lub odniesieniem do obiektu typu T lub odniesieniem do obiektu typu pochodzącego od T;

- ((* t1). * F) (t2, ..., tN), gdy f jest wskaźnikiem do funkcji składowej klasy T i t1 nie jest jednym z typów opisanych w poprzednim punkcie ;

- t1. * F gdy N == 1 if oznacza wskaźnik do danych prętów klasy T, a t1 jest obiektem typu T lub odniesieniem do obiektu typu T lub odniesieniem do obiektu typu pochodnego od T; .

- (* T1) * F gdy N == 1 i f jest wskaźnik do danych należących do klas T i T1 jest jednym z rodzajów opisanych w poprzednim akapicie;

- f (t1, t2, ..., tN) we wszystkich pozostałych przypadkach.

Teraz pojawia się pytanie:

Dlaczego C++ 11 standard definiuje obiekt, który sposób INVOKE?

A odpowiedź jest here.

+1

Dzięki, rozumiem, gdzie to zachowanie pochodzi od teraz, ale wciąż nie śledzę, dlaczego ... dlaczego dokładnie zdefiniowałbyś powiązanie z danymi członków w ten sposób? Po prostu można odzyskać członka danych przy użyciu std :: mem_fn? Jeśli powiążę f = std :: mem_fn (& X :: n), a następnie zadzwonię do f (& x), to spodziewam się albo błędu, albo xn(), a nie xn .. może być ważny przypadek użycia dla wiązania z danymi członków w ten sposób, ale dlaczego nie przez coś o nazwie std :: mem_data? –

+0

@StephenLin: Chociaż rozumiem twój punkt, wierzę, że SO nie nadaje się do pytań typu "* Dlaczego rzeczy są takie, jakimi są, a nie inne?". Próbowałem odpowiedzieć na część, na którą można obiektywnie odpowiedzieć. Mam nadzieję, że lepsze odpowiedzi zostaną udzielone. –

+0

Nie ma problemu, moim jedynym powodem, dla którego należy zapytać, jest to, że połączyłeś się z tą odpowiedzią jako wyjaśnieniem, które nie wydaje się niczego wyjaśniać (poza tym, że ktoś może uznać to przeciążone zachowanie za użyteczne ...) –

Powiązane problemy