2017-11-17 80 views
6

Rzućmy okiem na konkretnych funkcjach pierwsze:Uboczny referencje przekazując wątku klasy otoki

void boo1() { std::cout << "boo1\n"; } 
void boo2 (std::string) { std::cout << "boo2\n"; } 

struct X { 
    void boo3() { std::cout << "boo3\n"; } 
    void boo4 (std::string) { std::cout << "boo4\n"; } 
}; 

Chcę te funkcje mają być wykonane z jakiejś funkcji „strażnika”, który robi ochronę wyjątku (wyjaśnienie poniżej). Tak, napisałem dwie funkcje jeden z bezpłatnych funkcji, a inne dla funkcji składowych:

template <typename C, typename... Args> 
typename std::enable_if<!std::is_member_function_pointer<C>::value, void>::type 
foo (C&& c, Args&&... args) { 
    std::cout << "not memfun\n"; 
    c (std::forward<Args> (args)...); 
} 

template <typename C, typename T, typename... Args> 
typename std::enable_if<std::is_member_function_pointer<C>::value, void>::type 
foo (C&& c, T* o, Args&&... args) { 
    std::cout << "memfun\n"; 
    (o->*c) (std::forward<Args> (args)...); 
} 

a teraz chcę wykonać boo1(), boo2(), X::boo3() i X::boo4() w nowych wątków, ale z „ochrony” używanego przez foo() funkcji . Tak, bez problemu mogę to zrobić:

X x; 
std::thread th1 {&foo<void (*)()>, &boo1}; 
std::thread th2 {&foo<void (*) (std::string), std::string>, &boo2, "Hello"}; 
std::thread th3 {&foo<void (X::*)(), X>, &X::boo3, &x}; 
std::thread th4 {&foo<void (X::*) (std::string), X, std::string>, &X::boo4, &x, "Hello"}; 

So, idąc krok dalej napisałem wątek klasy otoki do zaprzestania używania wyraźnie std::join() lub std::detach(). Zainstalowana jedna z nich jest wywoływana, gdy destruktor jest w akcji. Dla uproszczenia tego przykładu użyłem tylko jednego z nich. Więc, kod wygląda następująco:

class Th { 
public: 
    template <typename C, 
       typename = typename std::enable_if<!std::is_member_function_pointer<C>::value, void>::type, 
       typename... Args> 
    Th (C&& c, Args... args) // (X) 
    : t {foo<C, Args...>, std::forward<C> (c), std::forward<Args> (args)...} {} 

    template <typename C, 
       typename = typename std::enable_if<std::is_member_function_pointer<C>::value, void>::type, 
       typename T, 
       typename... Args> 
    Th (C&& c, T* o, Args... args) // (Y) 
    : t { foo<C, T, Args...>, std::forward<C> (c), o, std::forward<Args> (args)...} {} 


    ~Th() { t.join(); } 

private: 
    std::thread t; 
}; 

I z tej implementacji nie ma problemu, następujące przypadki użycia:

X x; 
Th h1 {&boo1}; 
Th h2 {&boo2, "Hello"}; 
Th h3 {&X::boo3, &x}; 
Th h4 {&X::boo4, &x, "Hello"}; 

praca jako excepcted. Jest jednak jedna wada - nie ma idealnego przekazania. Jeśli zmienię w (X) i (Y)Args... do Args&&... który jest pożądany, kod nie skompilować z pewnymi błędami, jeden z nich to:

error: no type named ‘type’ in ‘class std::result_of<void (*(void (*)(std::basic_string<char>), const char*))(void (*&&)(std::basic_string<char>), const char (&)[6])>’ 

który jeśli rozumiem go poprawnie, std::bind() w std::thread konstruktora nie można zrobić to jest praca, albo popraw mnie, jeśli się mylę.

Moje pytanie brzmi: co mogę zrobić, aby skorzystać z idealnego przekazywania w tym przypadku?

Kompilatorem użytym do skompilowania tego pliku był GCC 4.8.1.

Promised wyjaśnienie: Chcę foo() funkcje otoczyć wywołanie c() z try i catch, aby upewnić się, że wyjątek jest przechwycony, proces tę informację w jakiś mechanizm rejestrowania i przekaż wyjątek w razie potrzeby z przejrzystości dla użytkownika klasy Th.

Odpowiedz

4

Podczas przekazywania wartości l-wartości do std::thread, należy zawinąć je w std::reference_wrapper. Na przykład, można to zrobić w następujący sposób:

namespace detail 
{ 

template<typename T> 
std::reference_wrapper<T> forward_to_thread(T& t, std::true_type) { 
    return std::ref(t); 
} 

template<typename T> 
T&& forward_to_thread(T&& t, std::false_type) { 
    return std::move(t); 
} 

} 

template<typename T> 
auto forward_to_thread(T&& t) 
    -> decltype(detail::forward_to_thread(std::forward<T>(t), std::is_lvalue_reference<T>{})) 
{ 
    return detail::forward_to_thread(std::forward<T>(t), std::is_lvalue_reference<T>{}); 
} 

Wtedy po prostu zastąpić std::forward wywołań forward_to_thread w thread inicjalizacji:

class Th { 
public: 
    template <typename C, 
       typename = typename std::enable_if<!std::is_member_function_pointer<C>::value, void>::type, 
       typename... Args> 
    Th (C&& c, Args&&... args) // (X) 
    : t {foo<C, Args...>, std::forward<C> (c), forward_to_thread<Args> (std::forward<Args>(args))...} {} 

    template <typename C, 
       typename = typename std::enable_if<std::is_member_function_pointer<C>::value, void>::type, 
       typename T, 
       typename... Args> 
    Th (C&& c, T* o, Args&&... args) // (Y) 
    : t { foo<C, T, Args...>, std::forward<C> (c), o, forward_to_thread<Args> (std::forward<Args>(args))...} {} 


    ~Th() { t.join(); } 

private: 
    std::thread t; 
}; 

to działa z gcc 4.8.2 [link]

przypadku można stosować nowsze kompilator, za pomocą std::invoke C++ (17) dla kodu więcej succint i czytelnej (tj this).

+0

Interesujące. Nie uda się skompilować, jeśli uruchomię go z 'std :: string (" Hello ")' zamiast zwykłym '" Hello "' w 'h2' i' h4'. Błąd polega na "nieznanej konwersji z 'std :: __ cxx11 :: basic_string ' na 'std :: __ cxx11 :: basic_string &&' dla pierwszego argumentu auto forward_to_thread (T && t)' ('clang 5.0'). – cantordust

+1

Argumenty 'forward_to_thread' również powinny zostać przekazane, naprawione. Dzięki. – krzaq

+0

To jest świetne, dziękuję –

Powiązane problemy