#c #c 11
#c #c 11 #stdvector #std-функция
Вопрос:
У меня есть несколько функций обратного вызова с разными сигнатурами. В идеале я хотел бы поместить их в вектор и вызвать соответствующий вектор в зависимости от определенных условий.
например ,
void func1(const std::stringamp; value);
void func2(const std::stringamp; value, int min, int max);
const std::vector<std::function<void(std::string)>> functions
{
func1,
func2,
};
Я понимаю, что вышесказанное невозможно, но мне интересно, есть ли какие-либо альтернативы, которые я должен рассмотреть. Я пока не смог найти ни одной, и я экспериментировал, std::bind
но не смог добиться того, чего хочу.
Возможно ли такое?
Комментарии:
1. Почему бы просто не поставить им одинаковые подписи? Вам не обязательно использовать все параметры в функции
2. Зачем вам нужно использовать вектор для этого? Вектор может содержать только типы, которые имеют один и тот же тип (учитывается полиморфизм). Если у вас есть функции с разными сигнатурами, вы не сможете этого сделать, например
for( autoamp;amp; function : functions){ function(); }
, поэтому вам все равно нужно разделить их или унифицировать их сигнатуры (но если у них разные сигнатуры, этот последний вариант выглядит как грубое принуждение их к вектору …)3. @EdHeal — Думаю, я мог бы. Просто задумался об альтернативах.
4. @JBL — В конечном счете, это то, что я хотел сделать. Я предполагаю, что вы говорите, что это невозможно.
5. Что именно ты хочешь сделать? Посмотрите на эти разные ответы.. Я проголосую за закрытие за «непонятно, о чем спрашиваю».
Ответ №1:
Вы не сказали, что вы ожидаете, что сможете сделать func2
после помещения его в вектор с неправильным типом.
Вы можете легко использовать std::bind
его для помещения в вектор, если заранее знаете аргументы:
const std::vector<std::function<void(std::string)>> functions
{
func1,
std::bind(func2, std::placeholders::_1, 5, 6)
};
Сейчас functions[1]("foo")
позвонит func2("foo", 5, 6)
, и передаст 5
и 6
func2
тому подобное.
Здесь то же самое, используя лямбда вместо std::bind
const std::vector<std::function<void(std::string)>> functions
{
func1,
[=](const std::stringamp; s){ func2(s, func2_arg1, func2_arg2); }
};
Если вы еще не знаете аргументы, вы можете привязать ссылки к некоторым переменным:
int func2_arg1 = 5;
int func2_arg2 = 6;
const std::vector<std::function<void(std::string)>> functions
{
func1,
std::bind(func2, std::placeholders::_1, std::ref(func2_arg1), std::ref(func2_arg2))
};
Теперь functions[1]("foo")
будет вызываться func2("foo", func2_arg1, func2_arg2)
, и вы можете присвоить новые значения целым числам для передачи разных аргументов func2
.
И использование лямбда-функции вместо std::bind
const std::vector<std::function<void(std::string)>> functions
{
func1,
[amp;](const std::stringamp; s){ func2(s, func2_arg1, func2_arg2); }
};
Однако это довольно некрасиво, поскольку вам нужно сохранять int
переменные до тех пор, пока вызываемый объект (замыкание или выражение привязки), ссылающийся на них, существует.
Комментарии:
1. Спасибо за ваш ответ. Использование std::bind так, как вы показали (поскольку я знаю аргументы во время компиляции), — это то, что я пробовал, когда сказал, что экспериментировал с std::bind, но не смог заставить его работать. Теперь я могу благодаря вашему ответу.
Ответ №2:
То, что вы хотите, возможно через polymorphism
. Идея состоит в том, чтобы создать класс с определенной сигнатурой, который во время выполнения будет вызывать разные методы. Например:
#include <iostream>
#include <functional>
#include <memory>
#include <vector>
void foo(int) {
std::cout << "I'm foo!n";
}
int bar(char, double) {
std::cout << "I'm bar!n";
}
class MyFunction {
public:
virtual ~MyFunction(){}
virtual void operator()() = 0;
};
class MyFunctionA : public MyFunction {
public:
virtual void operator()() {
foo(4);
}
};
class MyFunctionB : public MyFunction {
public:
MyFunctionB(std::function<int(char,double)> f, char arg1, double arg2) : fun_(f), arg1_(arg1), arg2_(arg2) {}
virtual void operator()() {
fun_(arg1_, arg2_);
}
private:
std::function<int(char,double)> fun_;
char arg1_;
double arg2_;
};
int main() {
using MyFunPtr = std::unique_ptr<MyFunction>;
std::vector<MyFunPtr> v;
v.emplace_back(new MyFunctionA());
v.emplace_back(new MyFunctionB(bar, 'c', 3.4));
for ( autoamp;amp; myfun : v ) {
(*myfun)();
}
return 0;
}
Вы можете сделать производные классы настолько сложными, насколько вам нужно, но поскольку в конечном итоге все они имеют один и тот же интерфейс, вы сможете вызывать их все.
Ответ №3:
Для C 17
std::variant может использоваться для хранения std::function
s с разными сигнатурами. В этом случае функция std::holds_alternative позволяет вам различать их во время выполнения:
Пример:
#include <variant>
#include <iostream>
#include <functional>
#include <vector>
using FooInt = std::function<void(int)>;
using FooStr = std::function<void(std::string)>;
using FooVariant = std::variant<FooInt, FooStr>;
void foo(int a){
std::cout << a << std::endl;
}
void bar(std::string a){
std::cout << a << std::endl;
}
int main()
{
std::vector<FooVariant> v;
v.push_back(foo);
v.push_back(bar);
for(autoamp; f: v){
if (std::holds_alternative<FooInt>(f))
std::get<FooInt>(f)(1);
else if (std::holds_alternative<FooStr>(f))
std::get<FooStr>(f)("hello");
}
}
Ответ №4:
Прямой ответ на ваш вопрос «НЕТ». Любой контейнер среды выполнения позволит вам хранить объекты только одного типа, а std::function<>, созданные с разными сигнатурами, будут иметь разные типы данных.
Как правило, причина, по которой вы можете захотеть иметь «вектор функций с разными сигнатурами», заключается в том, что у вас есть что-то вроде приведенного ниже (трехэтапная обработка, при которой входной интерфейс унифицирован ( bufferamp; buf
и выходной интерфейс унифицирован on_event(Event evt)
), но слой в середине неоднороден process_...(...)
receive_message(bufferamp; buf)
switch(msg_type(buf))
case A:
case B:
...
process_A(Aamp; a, One x, Two y)
...
dispatch(Event evt);
...
process_B(Bamp; b, Three x);
...
dispatch(Event evt);
...
В решении, не включающем метапрограммирование, вы обычно предварительно готовите функтор, выполняющий сквозную обработку во время инициализации, и сохраняете их в векторе:
vector <std::function<void(bufferamp; buf)>> handlers;
Ответ №5:
Если у вас есть int и string , вы не можете поместить их в один вектор, но вы можете поместить их в одну структуру или std::tuple<>
. То же самое относится к двум типам функций.
Комментарии:
1. это не вопрос
2. @BeyelerStudios: он явно запрашивает альтернативу
vector<>
для хранения двух функций. Неtuple<>
способен ли это сделать?3. под «количеством функций обратного вызова» я понимаю, что число является произвольным во время выполнения, кортежи здесь не подойдут
4. Даже тогда вполне вероятно, что набор довольно ограничен, и вы просто установите функции, которые вам не нужны
nullptr
.
Ответ №6:
std::function
стирает точный тип объекта функции, но сохраняет сигнатуру вызова функции. Если вы не можете bind
заранее указать дополнительные аргументы, как рекомендует Джонатан Уэйкли, вы можете использовать a boost::variant< std::function<...>, std::function<...> >
в качестве элемента вектора. Затем на сайте вызова вы можете проверить, содержит ли вектор объект функции правильного типа, и вызвать его соответствующим образом.
Комментарии:
1. Спасибо. Я смог получить то, что хотел, используя std::bind, следуя ответу Джонатана Уэйкли.
Ответ №7:
Не уверен, насколько это было бы полезно для вас, оно основано на boost::any
том, что избыточные параметры игнорируются. Вы можете добавить try...catch
for boost::bad_any_cast
, чтобы предотвратить сбой в случае несоответствия между типами аргументов и параметров. Хотя я думаю, что обычный std::bind
— лучший выбор.
#include <boost/any.hpp>
#include <functional>
#include <vector>
#include <cstddef>
#include <memory>
#include <tuple>
#include <utility>
#include <iostream>
#include <string>
struct IGenericFunction
{
virtual ~IGenericFunction() = default;
virtual void call(boost::any a1 = boost::any{}
, boost::any a2 = boost::any{}
, boost::any a3 = boost::any{}
, boost::any a4 = boost::any{}) = 0;
};
template <typename... Args>
class GenericFunction : public IGenericFunction
{
public:
GenericFunction(std::function<void(Args...)> f) : _f{ f } {}
virtual void call(boost::any a1 = boost::any{}
, boost::any a2 = boost::any{}
, boost::any a3 = boost::any{}
, boost::any a4 = boost::any{}) override
{
call_func(std::make_tuple(a1, a2, a3, a4)
, std::make_index_sequence<sizeof...(Args)>{});
}
private:
template <typename Tuple, std::size_t... Indices>
void call_func(Tuple t, std::index_sequence<Indices...> s)
{
_f(boost::any_cast<
typename std::tuple_element<Indices, Params>::type
>(std::get<Indices>(t))...);
}
std::function<void(Args...)> _f;
using Params = std::tuple<Args...>;
};
template <typename... Args>
std::shared_ptr<IGenericFunction> make_generic_function_ptr(void(*f)(Args...))
{
return std::make_shared<GenericFunction<Args...>>(f);
}
void func1(const std::stringamp; value)
{
std::cout << "func1 " << value << std::endl;
}
void func2(const std::stringamp; value, int min, int max)
{
std::cout << "func2 " << value << " " << min << " " << max << std::endl;
}
int main()
{
std::vector<std::shared_ptr<IGenericFunction>> functions;
functions.push_back(make_generic_function_ptr(amp;func1));
functions.push_back(make_generic_function_ptr(amp;func2));
for (auto f : functions)
{
f->call(std::string("abc"), 1, 2);
}
}
Ответ №8:
Как упоминал JBL: как бы вы их назвали, если вы не знаете их сигнатур?
Подумайте о том, чтобы превратить ваши min, max
аргументы в тип параметра с некоторым базовым классом Parameter
, сигнатурой обратного вызова будет void(const std::stringamp;, const Parameteramp;)
или void(const std::stringamp;, const Parameter*)
, если вы не хотите nullptr
указывать дополнительные параметры. Теперь вашим обратным вызовам необходимо будет проверить, были ли им заданы правильные параметры, если таковые имеются. Это может быть сделано с помощью visitor, typeid или enum . У всего этого есть свои плюсы и минусы.
Как вы решите, какой обратный вызов вызывать? Я думаю, вам следует превратить ваши обратные вызовы в стиле C в объекты-обработчики, они могут реализовать функцию bool canHandle(const Parameteramp;)
для проверки, применим ли обработчик к представленным параметрам.
Джонатан Уэйкли и Свалорцен представляют свой подход, в котором параметры и функция являются одним и тем же объектом (отношение 1 к 1). В этом примере они являются отдельными (в случае, если у вас есть отношения «несколько к нескольким»):
#include <cassert>
#include <string>
#include <typeinfo>
#include <vector>
class ParameterBase {
public:
ParameterBase(const std::stringamp; value) : m_value(value) { }
virtual ~ParameterBase() { }
const std::stringamp; GetValue() const { return m_value; }
private:
std::string m_value;
};
class HandlerBase {
public:
virtual bool CanHandle(const ParameterBaseamp; params) const = 0;
virtual void Handle(const ParameterBaseamp; params) = 0;
};
class Handler1 : public HandlerBase {
public:
class Parameter : public ParameterBase {
public:
Parameter(const std::stringamp; value) : ParameterBase(value) { }
~Parameter() { }
};
bool CanHandle(const ParameterBaseamp; params) const { return typeid(Parameter) == typeid(params); }
void Handle(const ParameterBaseamp; params) {
assert(CanHandle(params));
const Parameteramp; p = static_cast<const Parameteramp;>(params);
// implement callback1
}
};
void foo(const std::vector<HandlerBase*>amp; handlers) {
Handler1::Parameter params("value");
for(auto handler : handlers)
if(handler->CanHandle(params)) {
handler->Handle(params);
// no break: all handlers may handle params
// with break: only first handler (if any) handles params
}
}
Комментарии:
1. Как бы вы их назвали? Шаблон посетителя, например.
Ответ №9:
Я попытался использовать указатель на функцию и преобразовать std::function<int(int)>* в void*, он может быть успешно скомпилирован, но иногда это приводит к ошибке сегментации:
int Fun(int a)
{
std::cout << a << std::endl;
return ( a);
}
int main()
{
std::function<int(int)> originPFun = Fun;
void *ppFun;
// ppFun = (void*)amp;Fun; // This way will cause segmentation fault
ppFun = (void*)amp;originPFun; // This way can run seuccessful and get right result
std::function<int(int)> *pFun = (std::function<int(int)>*)(ppFun);
std::cout << (*pFun)(5) << std::endl;
system("pause");
return 0;
}