2016-08-22 12 views
13

Natknąłem się na to podczas moich eksperymentów z C++ 11. Uważam, że jest to oczywiste rozwiązanie, ale nie udało mi się znaleźć żadnych innych przykładów tego na wolności, więc obawiam się, że czegoś brakuje.Alternatywa dla std :: funkcja przekazywania funkcji jako argument (wywołania zwrotne, itp.)

Praktyka mam na myśli (w funkcji "addAsync"):

#include <thread> 
#include <future> 
#include <iostream> 
#include <chrono> 

int addTwoNumbers(int a, int b) { 
    std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl; 

    return a + b; 
} 

void printNum(std::future<int> future) { 
    std::cout << future.get() << std::endl; 
} 

void addAsync(int a, int b, auto callback(std::future<int>) -> void) { //<- the notation in question 
    auto res = std::async(std::launch::async, addTwoNumbers, a, b); 

    if (callback) //super straightforward nullptr handling 
     return callback(std::move(res)); 
} 

int main(int argc, char** argv) { 
    addAsync(10, 10, [](std::future<int> number) { //lambda functions work great 
     addAsync(number.get(), 20, [](std::future<int> number) { 
      addAsync(893, 4387, printNum); //as do standard functions 
      addAsync(2342, 342, nullptr); //executes, sans callback 

      std::cout << number.get() << std::endl; 
     }); 
    }); 

    std::cout << "main thread: " << std::this_thread::get_id() << std::endl; 

    return 0; 
} 

Jest uważany za złą praktykę, czy jest to non-przenośne (Ja tylko próbowałem w MSVC++ 2015) ? Ponadto, w jaki sposób traktuje to kompilator; przez konwersję na std :: function?

Chciałbym nadal używać tego w moich projektach, ponieważ oczywiście określa wymagane typy argumentów i typ zwracania w "podpisie", akceptuje nullptr dla opcjonalności i wydaje się "po prostu działać" (jestem świadom tego, że to są słynne ostatnie słowa w C++).

+2

@Amadeus: Może to zdania, które kończą się znakami zapytania. Na SO jest wiele pytań, które nie zostały sformułowane; to jest * nie * jeden z nich. –

+3

Parametr jest zadeklarowany jako posiadający funkcję typu 'void (std :: future )', która jest dostosowana do wskaźnika do funkcji 'void (*) (std :: future )'. –

Odpowiedz

11

You korzystania z surowego wskaźnik funkcjonować.

przeciwieństwie std::function, to nie będzie działać z lambda, który przechwytuje, lub w wyniku std::bind lub z ogólnego typu klasy, która implementuje operator().

+0

Ach, miałem nadzieję, że to nie będzie surowy wskaźnik. Dziękuję bardzo za wyjaśnienie. Ograniczenia dotyczące: przechwytywania są w wielu przypadkach przełamujące umowę i dla spójności uważam, że będę trzymać się std :: function i std :: bind. Zaznaczę to jako odpowiedź! –

1

Alternatywa dla std :: funkcję przekazywania funkcji jako argumentu”

Alternatywą byłby wskaźnik funkcji (w tym członek wskaźnik funkcji). Ale std::function jest tak dużo ładniejszy (IMO).

14

auto callback(std::future<int>) -> void to deklaracja podmiotu typu void(std::future<int>) o nazwie callback. Jeśli zostanie wymieniony jako argument, kompilator dostosuje go do wskaźnika dla funkcji typu void(*)(std::future<int>).

Twoja lambda jest bezstanowa i jako taka może zostać niejawnie przekształcona w wskaźnik funkcji.

Po dodaniu nietrywialne przechwytywanie kod przestanie kompilacji:

[argc](std::future<int> number) { 
    std::cout << argc << '\n'; 

...

Teraz ignorując treść zapytania i patrząc na tytuł ...

Istnieje niewielki koszt dla std::function, ponieważ jest to typ wartości, a nie typ widoku. Jako typ wartości, faktycznie kopiuje swój argument.

Można obejść ten problem poprzez owinięcie obiekt wywołujący w std::ref, ale jeśli chcesz, aby stwierdzić „Nie będę tego obiektu funkcyjnego wokół dłużej niż to wezwanie”, można napisać typ function_view następująco:

template<class Sig> 
struct function_view; 

template<class R, class...Args> 
struct function_view<R(Args...)> { 
    void* ptr = nullptr; 
    R(*pf)(void*, Args...) = nullptr; 

    template<class F> 
    using pF = decltype(std::addressof(std::declval<F&>())); 

    template<class F> 
    void bind_to(F& f) { 
    ptr = (void*)std::addressof(f); 
    pf = [](void* ptr, Args... args)->R{ 
     return (*(pF<F>)ptr)(std::forward<Args>(args)...); 
    }; 
    } 
    // when binding to a function pointer 
    // even a not identical one, check for 
    // null. In addition, we can remove a 
    // layer of indirection and store the function 
    // pointer directly in the `void*`. 
    template<class R_in, class...Args_in> 
    void bind_to(R_in(*f)(Args_in...)) { 
    using F = decltype(f); 
    if (!f) return bind_to(nullptr); 
    ptr = (void*)f; 
    pf = [](void* ptr, Args... args)->R{ 
     return (F(ptr))(std::forward<Args>(args)...); 
    }; 
    } 
    // binding to nothing: 
    void bind_to(std::nullptr_t) { 
    ptr = nullptr; 
    pf = nullptr; 
    }  
    explicit operator bool()const{return pf;} 

    function_view()=default; 
    function_view(function_view const&)=default; 
    function_view& operator=(function_view const&)=default; 

    template<class F, 
    std::enable_if_t< !std::is_same<function_view, std::decay_t<F>>{}, int > =0, 
    std::enable_if_t< std::is_convertible< std::result_of_t< F&(Args...) >, R >{}, int> = 0 
    > 
    function_view(F&& f) { 
    bind_to(f); // not forward 
    } 

    function_view(std::nullptr_t) {} 

    R operator()(Args...args) const { 
     return pf(ptr, std::forward<Args>(args)...); 
    } 
}; 

live example.

Jest to również użyteczne, ponieważ jest to prostszy rodzaj wymazywania typu niż std::function, więc może to być edukacyjne, aby przejść do niego.

+0

Dziękujemy za rozszerzenie w odpowiedzi aschepler. Kolejny przykład uchwytów kompilatora. –

+1

s/variable/entity /. Nie ma zmiennych o typie funkcji. –

Powiązane problemy