2015-10-18 10 views
5

Tworzę kolejkę zadań. Zadanie zostanie utworzone w wątku A, następnie zadanie zostanie wysłane do wątku B, a wątek B wykona zadanie. Po wykonaniu zadania zadanie zostanie odesłane do wątku A.Zbyt wiele kopii podczas wiązania argumentów szablonu variadic

#include <functional> 
#include <iostream> 
#include <memory> 

using namespace std; 

template<typename T, typename... Args> 
class Job 
{ 
    public: 
     Job(std::weak_ptr<T> &&wp, std::function<void(const Args&...)> &&cb) 
      : _cb(std::move(cb)), 
       _cbWithArgs(), 
       _owner(std::move(wp)) {} 

    public: 
     template<typename... RfTs> 
     void bind(RfTs&&... args) 
     { 
      // bind will copy args for three times. 
      _cbWithArgs = std::bind(_cb, std::forward<RfTs>(args)...); 
     } 

     void fire() 
     { 
      auto sp = _owner.lock(); 
      if (sp) 
      { 
       _cbWithArgs(); 
      } 
     } 

    private: 
     std::function<void(const Args& ...)> _cb; 
     std::function<void()> _cbWithArgs; 
     std::weak_ptr<T> _owner; 
}; 

struct Args 
{ 
    Args() = default; 
    Args(const Args &args) 
    { 
     cout << "Copied" << endl; 
    } 
}; 

struct Foo 
{ 
    void show(const Args &) 
    { 
     cout << "Foo" << endl; 
    } 
}; 

int main() 
{ 
    using namespace std::placeholders; 

    shared_ptr<Foo> sf (new Foo()); 
    Args args; 

    // Let's say here thread A created the job. 
    Job<Foo, Args> job(sf, std::bind(&Foo::show, sf.get(), _1)); 

    // Here thread B has finished the job and bind the result to the 
    // job. 
    job.bind(args); 

    // Here, thread A will check the result. 
    job.fire(); 
} 

Powyższe kody się kompilują i działają. Ale daje następujące wyniki (g ++ 4.8.4 & dzyń mają takie same wyniki):

Copied 
Copied 
Copied 
Foo 

Istnieją trzy egzemplarze! Nie do przyjęcia, nie wiem, gdzie popełniłem błąd. Dlaczego trzy egzemplarze? Wyszukałem go i znalazłem metodę z tego miejsca: https://stackoverflow.com/a/16868401, to tylko kopiuje parametry jednorazowo. Ale musi zainicjować funkcję wiązania w konstruktorze.

Dzięki, Piotr Skotnicki. Niestety nie mam kompilatora C++ 14. Więc zróbmy args ruchome:

struct Args 
{ 
    Args() = default; 
    Args(const Args &args) 
    { 
     cout << "Copied" << endl; 
    } 
    Args(Args &&) = default; 
    Args& operator=(Args &&) = default; 
}; 

Teraz copys tylko jeden raz :)

Wreszcie przyjęte kody z tego wątku https://stackoverflow.com/a/16868151/5459549. Szablon gen_seq jest prawdziwą ART, muszę powiedzieć.

Odpowiedz

5

Pierwszy egzemplarz wykonany przez samą std::bind:

std::bind(_cb, std::forward<RfTs>(args)...) 

gdyż musi przechowywać zbutwiałe-kopie swoich argumentów.

Pozostałe dwie kopie przez przypisanie do _cbWithArgs który jest typu std::function (§ 20.8.11.2.1 [func.wrap.func.con]):

template<class F> function& operator=(F&& f); 

Efekty:function(std::forward<F>(f)).swap(*this);

gdzie:

template<class F> function(F f); 

[...] *this cele Kopia f inicjowany std::move(f).

To jest jedna kopia dla parametru konstruktora, a druga dla przechowywania argumentu f.

Ponieważ Args jest nieruchomej typu próba ruchu z wynikiem wywołania std::bind wraca do kopii.

W kodzie nie ma sensu przechowywanie wyników wykonywania pracy w postaci std::function.Zamiast tego można przechowywać te wartości w krotce:

template <typename T, typename... Args> 
class Job 
{ 
public: 
    Job(std::weak_ptr<T> wp, std::function<void(const Args&...)> &&cb) 
     : _cb(std::move(cb)), 
      _owner(wp) {} 

public: 
    template<typename... RfTs> 
    void bind(RfTs&&... args) 
    { 
     _args = std::forward_as_tuple(std::forward<RfTs>(args)...); 
    } 

    void fire() 
    { 
     auto sp = _owner.lock(); 
     if (sp) 
     { 
      apply(std::index_sequence_for<Args...>{}); 
     } 
    } 

private:  
    template <std::size_t... Is> 
    void apply(std::index_sequence<Is...>) 
    { 
     _cb(std::get<Is>(_args)...); 
    } 

    std::function<void(const Args&...)> _cb; 
    std::weak_ptr<T> _owner; 
    std::tuple<Args...> _args; 
}; 

DEMO

+0

niestety ja nie kompiluje w VC14, std :: krotka musi zainicjować – MORTAL

+0

@MORTAL * "Niestety nie kompiluje się VC14, std :: tuple musi zainicjować "*, czy możesz rozwinąć? –

+0

dziękuję za odpowiedź informacyjną, uruchomiłem twój kod w VC14, który nie skompilował, daje błąd z powrotem jako 'tuple (67): błąd C2476: konstruktor 'constexpr' nie inicjuje wszystkich członków'. nie wiem, czy to może być kolejny błąd z VC14 – MORTAL

Powiązane problemy