2013-02-12 2 views
4

Для библиотеки я хотел бы, чтобы функция приняла другую функцию и ее аргументы, а затем сохранила их для вызова позже. Аргументы должны допускать любую смесь типов, но функции должны возвращать только пустоту. Что-то вроде этого:Чистый способ хранения функции и ее аргументов (произвольного типа, произвольного числа)

void myFunc1(int arg1, float arg2); 
void myFunc2(const char *arg1); 
class DelayedCaller 
{ ... 
public: 
    static DelayedCaller *setup(Function func, …); 
}; 

... 
DelayedCaller* caller1 = DelayedCaller::setup(&myFunc1, 123, 45.6); 
DelayedCaller* caller2 = DelayedCaller::setup(&myFunc2, "A string"); 

caller1->call(); // Calls myFunc1(), with arguments 123 and 45.6 
caller2->call(); // Calls myFunc2(), with argument "A string" 

Один подход, чтобы сделать DelayedCaller :: Настройка() принимают зЬй :: функции, и есть мои пользователи библиотеки используют зЬй :: Bind() перед вызовом установки(). Однако есть ли способ реализовать setup(), чтобы пользователям не нужно было связывать себя?

Редактировать: DelayedCaller - это существующий класс. setup() - это новый статический метод, который я бы хотел добавить.

+0

Вы можете использовать std :: async с ленивой оценкой. Он возвращает объект std :: future, который сохраняет задачу позже. – Snps

ответ

7

возможность заключается в том, чтобы использовать VARIADIC шаблоны и вызвать std::bind() внутри setup() функции:

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

void myFunc1(int arg1, float arg2) 
{ 
    std::cout << arg1 << ", " << arg2 << '\n'; 
} 
void myFunc2(const char *arg1) 
{ 
    std::cout << arg1 << '\n'; 
} 

class DelayedCaller 
{ 
public: 
    template <typename TFunction, typename... TArgs> 
    static std::unique_ptr<DelayedCaller> setup(TFunction&& a_func, 
               TArgs&&... a_args) 
    { 
     return std::unique_ptr<DelayedCaller>(new DelayedCaller(
      std::bind(std::forward<TFunction>(a_func), 
         std::forward<TArgs>(a_args)...))); 
    } 
    void call() const { func_(); } 

private: 
    using func_type = std::function<void()>; 
    DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {} 
    func_type func_; 
}; 

int main() 
{ 
    auto caller1(DelayedCaller::setup(&myFunc1, 123, 45.6)); 
    auto caller2(DelayedCaller::setup(&myFunc2, "A string")); 

    caller1->call(); 
    caller2->call(); 

    return 0; 
} 

Выход:

 
123, 45.6 
A string 

Вернуть смарт-указатель, например, как std::unique_ptr, вместо того чтобы вернуться сырым указатель (или возврат по значению и исключение динамического выделения). func_type является подвижным, если аргументы являются подвижными, или может быть довольно дешевым для копирования в любом случае. Возможно, вам нужно будет определить конструктор перемещения и переместить присваивание nt, они генерируются при определенных условиях).

+0

+1 для 'unique_ptr' и templated функции. Я забыл об этом. – Fiktik

+0

Это прекрасно; Благодарю. Дополнительная благодарность за подробную интеграцию безопасности указателя и семантики перемещения. – JKSH

0

Если вы хотите/можете использовать в C++ 11 будущее библиотеки можно использовать std::async

#include <future> 

auto caller = std::async(myFunc1, 123, 45.6); // Creates a future object. 

caller.get(); // Waits for the function to get executed and returns result. 

Чтобы заставить ленивую использовать оценки:

auto caller = std::async(std::launch::deferred, myFunc1, 123, 45.6); 

Кроме того, это имеет преимущество в том, что вызов функции может выполняться в другом потоке, который использует многоядерное оборудование. Однако это может оказаться неприемлемым в каждом случае.

+0

Это не подходит для моего случая, так как я расширяю пользовательский API-интерфейс управления потоками :) Тем не менее, спасибо за демонстрацию использования std :: async(), который был для меня новичком – JKSH

7

Вы можете использовать лямбда-функции, чтобы скрыть привязок:

#include <functional> 

class DelayedCaller : public std::function< void(void) > { 
public: 
    DelayedCaller(std::function< void(void) > fn) 
     : std::function< void(void) >(fn) {} 
}; 

DelayedCaller caller1([]() { myFunc1(123, 45.6); }); 
DelayedCaller caller2([]() { myFunc2("A string"); }); 

caller1(); // Calls myFunc1(), with arguments 123 and 45.6 
caller2(); // Calls myFunc2(), with argument "A string" 

Это также дает пользователям вашей библиотеки больше гибкости. Они не ограничиваются вызовом одной функции и функции имеют доступ к исходной среде они были созданы в:

int x; 

DelayedCaller caller3 = [&x]() { 
    if (x == 0) 
     DoSomething(); 
    else 
     DoSomethingElse(); 
}; 
+0

Это, безусловно, маршрут I рекомендовал бы использовать 'std :: async'. – moswald

+0

Я не упоминал об этом в своем вопросе, но DelayedCaller должен быть классом, поскольку он отвечает за другие вещи в моей библиотеке.Тем не менее, спасибо за размышление вне коробки! – JKSH

+0

@JKSH - Хорошо, я сделал это класс. – Ferruccio

0

Если единственная забота, чтобы скрыть аргумент связывания с callsite, сохраняя при этом свой интерфейс, использование VARIADIC шаблоны

class DelayedCaller 
{ 
public: 
    template<typename... Args> 
    static DelayedCaller* setup(void (functionPtr*)(Args...), Args&&... args) 
    { 
    return new DelayedCaller(std::bind(functionPtr, std::forward<Args>(args)...)); 
    } 

    DelayedCaller(const std::function<void()>& f) : f(f) {} 

private: 
    std::function<void()> f; 
}; 

публичный конструктор по-прежнему предлагает пользователям возможность инициализировать его с лямбда они хотят.

+0

Да, простой интерфейс был моей целью. Я выбрал ответ hmjd за его дополнительную надежность, но этот минималистический пример хорош для начинающих, таких как я; Спасибо, что поделился. – JKSH

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