2015-06-15 4 views
6

Еще в 2010 году Херб Саттер выступал за использование активных объектов вместо обнаженных потоков в article на докторе Доббе.Возвращаемые значения для активных объектов

Вот версия С ++ 11:

class Active { 
public: 
    typedef std::function<void()> Message; 

    Active(const Active&) = delete; 
    void operator=(const Active&) = delete; 

    Active() : done(false) { 
     thd = std::unique_ptr<std::thread>(new std::thread([=]{ this->run(); })); 
    } 

    ~Active() { 
     send([&]{ done = true; }); 
     thd->join(); 
    } 

    void send(Message m) { mq.push_back(m); } 

private: 
    bool done; 
    message_queue<Message> mq; // a thread-safe concurrent queue 
    std::unique_ptr<std::thread> thd; 

    void run() { 
     while (!done) { 
      Message msg = mq.pop_front(); 
      msg(); // execute message 
     } // note: last message sets done to true 
    } 
}; 

Класс может быть использован, как это:

class Backgrounder { 
public: 
    void save(std::string filename) { a.send([=] { 
     // ... 
    }); } 

    void print(Data& data) { a.send([=, &data] { 
     // ... 
    }); } 

private: 
    PrivateData somePrivateStateAcrossCalls; 
    Active a; 
}; 

Я хотел бы поддержать функции-членов с Непустыми типами возврата. Но я не могу придумать хороший дизайн, как реализовать это, т. Е. Без использования контейнера, который может содержать объекты гетерогенных типов (например, boost::any).

Любые идеи приветствуются, особенно ответы, которые используют возможности C++ 11, такие как std::future и std::promise.

+1

Вы просите (нелюбимый) std :: async? – stefan

+0

Если я прочитал справочное право, 'std :: async' запускает новые потоки или использует пул потоков. Я хотел бы поставить очереди в один поток для последовательного выполнения. – enkelwor

ответ

5

Это займет определенную работу.

Во-первых, напишите task<Sig>. task<Sig> - std::function, который ожидает, что его аргумент будет подвижный, не подлежит копированию.

Ваш внутренний типMessages будет task<void()>. Таким образом, вы можете быть ленивым и иметь task только поддержку нулевых функций, если хотите.

Во-вторых, отправить создает std::packaged_task<R> package(f);. Затем он выводит будущее из задачи, а затем перемещает package в очередь сообщений. (Вот почему вам нужно только движение std::function, потому что packaged_task можно перемещать только).

Затем вы возвращаете future из packaged_task.

template<class F, class R=std::result_of_t<F const&()>> 
std::future<R> send(F&& f) { 
    packaged_task<R> package(std::forward<F>(f)); 
    auto ret = package.get_future(); 
    mq.push_back(std::move(package)); 
    return ret; 
} 

клиенты могут захватить Ахольд из std::future и использовать его (позже) получить результат обратного вызова.

Занятно, вы можете написать очень простой шаг, только нульарную задачу следующим образом:

template<class R> 
struct task { 
    std::packaged_task<R> state; 
    template<class F> 
    task(F&& f):state(std::forward<F>(f)) {} 
    R operator()() const { 
    auto fut = state.get_future(); 
    state(); 
    return f.get(); 
    } 
}; 

но это смехотворно неэффективно (упаковано задача имеет материал синхронизации в нем), и, вероятно, нуждается в очистке. Я нахожу это забавным, потому что он использует packaged_task для части только для перемещения std::function.

Лично я столкнулся с достаточным основанием для решения задач только для перемещения (среди этой проблемы), чтобы почувствовать, что стоит только переместить только std::function. Ниже следует одна такая реализация. Он не сильно оптимизирован (вероятно, примерно так же быстро, как наиболее std::function однако), и не отлажена, но дизайн звук:

template<class Sig> 
struct task; 
namespace details_task { 
    template<class Sig> 
    struct ipimpl; 
    template<class R, class...Args> 
    struct ipimpl<R(Args...)> { 
    virtual ~ipimpl() {} 
    virtual R invoke(Args&&...args) const = 0; 
    }; 
    template<class Sig, class F> 
    struct pimpl; 
    template<class R, class...Args, class F> 
    struct pimpl<R(Args...), F>:ipimpl<R(Args...)> { 
    F f; 
    R invoke(Args&&...args) const final override { 
     return f(std::forward<Args>(args)...); 
    }; 
    }; 
    // void case, we don't care about what f returns: 
    template<class...Args, class F> 
    struct pimpl<void(Args...), F>:ipimpl<void(Args...)> { 
    F f; 
    template<class Fin> 
    pimpl(Fin&&fin):f(std::forward<Fin>(fin)){} 
    void invoke(Args&&...args) const final override { 
     f(std::forward<Args>(args)...); 
    }; 
    }; 
} 
template<class R, class...Args> 
struct task<R(Args...)> { 
    std::unique_ptr< details_task::ipimpl<R(Args...)> > pimpl; 
    task(task&&)=default; 
    task&operator=(task&&)=default; 
    task()=default; 
    explicit operator bool() const { return static_cast<bool>(pimpl); } 

    R operator()(Args...args) const { 
    return pimpl->invoke(std::forward<Args>(args)...); 
    } 
    // if we can be called with the signature, use this: 
    template<class F, class=std::enable_if_t< 
    std::is_convertible<std::result_of_t<F const&(Args...)>,R>{} 
    >> 
    task(F&& f):task(std::forward<F>(f), std::is_convertible<F&,bool>{}) {} 

    // the case where we are a void return type, we don't 
    // care what the return type of F is, just that we can call it: 
    template<class F, class R2=R, class=std::result_of_t<F const&(Args...)>, 
    class=std::enable_if_t<std::is_same<R2, void>{}> 
    > 
    task(F&& f):task(std::forward<F>(f), std::is_convertible<F&,bool>{}) {} 

    // this helps with overload resolution in some cases: 
    task(R(*pf)(Args...)):task(pf, std::true_type{}) {} 
    // = nullptr support: 
    task(std::nullptr_t):task() {} 

private: 
    // build a pimpl from F. All ctors get here, or to task() eventually: 
    template<class F> 
    task(F&& f, std::false_type /* needs a test? No! */): 
    pimpl(new details_task::pimpl<R(Args...), std::decay_t<F>>{ std::forward<F>(f) }) 
    {} 
    // cast incoming to bool, if it works, construct, otherwise 
    // we should be empty: 
    // move-constructs, because we need to run-time dispatch between two ctors. 
    // if we pass the test, dispatch to task(?, false_type) (no test needed) 
    // if we fail the test, dispatch to task() (empty task). 
    template<class F> 
    task(F&& f, std::true_type /* needs a test? Yes! */): 
    task(f?task(std::forward<F>(f), std::false_type{}):task()) 
    {} 
}; 

live example.

- первый эскиз в объекте задачи только для объекта класса библиотеки. Он также использует некоторые вещи C++ 14 (псевдонимы std::blah_t) - замените std::enable_if_t<???> на typename std::enable_if<???>::type, если вы являетесь компилятором C++ 11.

Обратите внимание, что трюк типа возврата void содержит некоторые незначительно сомнительные трюки с перегрузкой шаблона. (Можно утверждать, что оно является законным по формулировке стандарта, но каждый компилятор C++ 11 примет его, и он, скорее всего, станет законным, если это не так).

Смежные вопросы