Массив функций с разными сигнатурами - PullRequest
5 голосов
/ 07 сентября 2011

У меня есть следующие классы:

class Foo
{
    ...
};

class Foo1 : public Foo
{
    ...
};

...

class FooN : public Foo
{
    ...
};

Возможно ли иметь массив функций с такими сигнатурами:

void f1(Foo1*){}
...
void fN(FooN*){}

Есть ли изменения, если эти функции не являютсястатические функции-члены вместо обычных функций?Я не думаю, что это что-то изменит.

Спасибо!

Ответы [ 10 ]

3 голосов
/ 07 сентября 2011

РЕДАКТИРОВАТЬ альтернативное решение, не основанное на виртуальных функциях здесь .

Тип void(*)(Foo*) не может быть преобразован в тип void(*)(Bar*) и по уважительной причине.

Вы должны заставить все свои функции принимать аргумент Interface*, и все FooN должны быть получены из Interface

struct Interface {
    virtual ~ Interface () {}
    // ...
};

struct Foo1 : public Interface {
    // ...
};

struct Foo2 : public Interface {
    // ...
};

void f1 (Interface *);
void f2 (Interface *);

void (*functions)(Interface*) [] = {f1, f2};

functions[0] (new Foo1 ());
functions[0] (new Foo2 ());
functions[1] (new Foo1 ());
functions[1] (new Foo2 ());

Реализации f1, f2 могут проверять во время выполнения, еслиих аргументом является конкретная реализация с использованием dynamic_cast и проверкой на nullptr.Единственный способ проверить во время компиляции - заставить f1 и f2 принимать определенные типы, а , а не помещать их в анонимный массив, но вызывать их явно.


Чтобы ответить на вторую часть вашего вопроса - да, это НУЖНО, если они нестатические функции-члены, потому что размер указателя не постоянен

2 голосов
/ 07 сентября 2011

Вы можете использовать функциональные объекты. Смотрите пример ниже, как это сделать самостоятельно. Если вам нравится идея, вы должны взглянуть на boost.signal / boost.bind и его аналоги из c ++ 0x.

class Foo1 {};
class Foo2 {};
class Foo3 {};

void func1(Foo1*) {}
void func2(Foo2*) {}
void func3(Foo3*) {}

class FuncObjBase {
public:
    virtual void operator()() = 0;
};

template <class T>
class FuncObj : public FuncObjBase {
public:
    typedef void (*Funcptr)(T*);
    FuncObj(T* instance, Funcptr funcptr) : m_Instance(instance), m_Func(funcptr) {}
    virtual void operator()() { m_Func(m_Instance); }
private:
   T* m_Instance;
   Funcptr m_Func;
};

int main(int argc, char *argv[])
{
    Foo1 foo1;
    Foo2 foo2;
    Foo3 foo3;
    FuncObjBase* functions[3];
    functions[0] = new FuncObj<Foo1>(&foo1, func1);
    functions[1] = new FuncObj<Foo2>(&foo2, func2);
    functions[2] = new FuncObj<Foo3>(&foo3, func3);
    for(unsigned int i = 0; i < 3; i++) {
        (*functions[i])();
    }
    return 0;
}
1 голос
/ 21 ноября 2015

Вы можете сделать это в C ++ 11 с помощью шаблонов Variadic.Проверьте мой ответ, который похож на то, что вы хотите, но с картами на: https://stackoverflow.com/a/33837343/1496826

1 голос
/ 07 сентября 2011

C ++ - статически типизированный язык, который включает в себя типы функций. В каждой строке кода компилятор C ++ должен быть в состоянии определить, действительна ли сигнатура функции и какую функцию (или указатель) вызывать.

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

Абсолютно лучшее, что вы можете сделать, это использовать что-то вроде массива boost::variant. Вы можете иметь определенный набор прототипов функций, сохраненных в варианте, возможно, используя boost::function. Однако это будет только ограниченное множество, а не произвольный тип функции. И вызвать их было бы довольно сложно, так как вы должны сначала убедиться, что вариант действительно имеет ожидаемый тип функции, а затем вызвать его.

Другая альтернатива - использовать массив boost::any. Кроме этого, типы могут быть любыми типами функций. Опять же, вызов его потребует преобразования его в один из ожидаемых типов функций. Проблема усложняется, поскольку типы функций могут быть буквально чем угодно . Поэтому вам придется предоставить запасной вариант, если он не относится к ожидаемым типам функций.

Если список функций небольшой и определяется во время компиляции, вы можете использовать boost::tuple в качестве временного «массива». Однако вы должны использовать метапрограммирование шаблонов для их итерации. Конечно, если бы это было так, вы могли бы просто использовать структуру, содержащую указатели на функции соответствующего типа.

0 голосов
/ 08 сентября 2011

Вот общий подход, который является типобезопасным и заставляет код клиента быть правильным.

class Manager {
public:

    typedef int /* or whatever */ ID;

    template <typename Function>
    static void save (Function * f, ID id) {
        functions <Function> () .add (id, f);
    }

    template <typename Function>
    static Function * get (ID id) {
        return functions <Function> () .get (id);
    }

private:

    template <typename Function>
    class FunctionStore {
    public:

         void add (Function *, ID);
         Function * get (ID);

    private:
         // or vector, if you know ID is int.
         std :: map <ID, Function *> m_functions;
    };

    // type_index is C++11 but you can implement it in C++03.
    // void* here is unpleasant but you can improve it, RAII it.
    typedef std :: map <std :: type_index, void *> Store;
    static Store m_store;

    template <typename Function>
    FunctionStore <Function> & functions () {
        FunctionStore <Function> * fs;

        Store :: iterator i = m_store .find (typeid Function);

        if (m_store .end () == i) {
            fs = new FunctionStore <Function> ();
            m_store [typeid Function] = fs;
        }
        else {
            // This void* cast is OK because it's internally controlled
            // and provably correct.
            // We don't have to trust the library to not abuse it.
            fs = static_cast <FunctionStore<Function>*> (i -> second);
        }

        return *fs;
    }
};

// In the library

void foo1 (Foo *);
void bar1 (Bar *);
void foo2 (Foo *);
void bar2 (Bar *);

void init () {
    Manager :: save (foo1, 1);
    Manager :: save (foo2, 2);
    Manager :: save (bar1, 1);
    Manager :: save (bar2, 2);

    Manager :: get <void(Foo*)> (1) (new Foo ()); // OK, calls foo1
    Manager :: get <void(Foo*)> (1) (new Bar ()); // Will not compile
    Manager :: get <void(Bar*)> (2) (new Bar ()); // OK, calls bar2
}

Если вам не нужны накладные расходы при поиске в m_store (и / или вы хотите избежать void в Manager::Store), вы можете сделать Manager самим классом шаблона, недостатком теперь является вы нужно следить за вашими статическими m_store определениями. Это нормально, если вы знаете, что клиенты будут использовать только данный набор Function подписей.

void init () {
    Manager <void(Foo*)> :: save (foo1, 1);
    Manager <void(Foo*)> :: save (foo2, 2);
    Manager <void(Foo*)> :: save (bar1, 1); // Won't compile
    Manager <void(Bar*)> :: save (bar1, 1);
    Manager <void(Bar*)> :: save (bar2, 2);

    Manager <void(Foo*)> :: get (1) (new Foo ()); // OK, calls foo1
    Manager <void(Foo*)> :: get (1) (new Bar ()); // Will not compile
    Manager <void(Bar*)> :: get (2) (new Bar ()); // OK, calls bar2
}

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

0 голосов
/ 08 сентября 2011

Я бы предложил использовать std::tuple вместо std::array или C-массива.Используя std::tuple, вы можете хранить элементы разных типов.

0 голосов
/ 07 сентября 2011

Я нашел этот обходной путь для этой проблемы:

#include <iostream>
#include <vector>

class Foo
{
};

class Foo1 : public Foo
{
};

class Foo2 : public Foo
{
};

class Foo3 : public Foo
{
};


void f1(Foo1*)
{
    std::cout<<"f1\n";
}

void f2(Foo2*)
{
    std::cout<<"f2\n";
}

void f3(Foo3*)
{
    std::cout<<"f3\n";
}

template<typename T>
void AddPointer(std::vector<typename void (*)(Foo*)>& fPointers, T function)
{
    fPointers.push_back(reinterpret_cast<void (*)(Foo*)>(function));
}

void main()
{
    std::vector<typename void (*)(Foo*)> fPointers;

    AddPointer(fPointers, f1);
    AddPointer(fPointers, f2);
    AddPointer(fPointers, f3);

    Foo1 foo1;
    Foo2 foo2;
    Foo3 foo3;

    fPointers[0](&foo1);
    fPointers[1](&foo2);
    fPointers[2](&foo3);
}
0 голосов
/ 07 сентября 2011

То, что вы хотите, это ковариантные типы аргументов .Это не поддерживается в C ++, потому что это нарушает безопасность типов.Чтобы лучше понять это, давайте рассмотрим простой пример:

struct Vehicle {};
struct Tricycle : Vehicle {};
struct Tank : Vehicle {};

void drive(Vehicle const & b) { ... }
void giveToChild(Tricycle const & b) { ... }

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

typedef void (*funcPtr)(Vehicle const &);

funcPtr = &giveToChild; // this is not allowed
funcPtr(Tank());        // oops, I just gave a tank to my child!

Язык мог бы реализовать некоторую проверку типов во время выполнения, но это не так, как работает C ++.Однако обратное преобразование (контравариантность) может быть разрешено без каких-либо проблем (на самом деле, делегаты C # допускают это), но это невозможно в C ++ по некоторым причинам, о которых я не знаю.Вот пример того, что было бы разрешено:

typedef void (*funcPtr)(Tricycle const &);

funcPtr = &drive;    // this could be allowed, but is not (in C++)
funcPtr(Tricycle()); // I can only drive a tricycle, but that's ok since it's a
                     // vehicle and I know how to drive all vehicles

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

void forwardFN(Foo * f)
{
    FooN * instance = dynamic_cast<FooN *>(f);

    if (instance) fN(instance);
    else throw type_exception();
}
0 голосов
/ 07 сентября 2011

Вы можете сделать функции f1 - fN членами их определенных классов аргументов, присвоить им одинаковые имена и использовать виртуальную диспетчеризацию для вызова нужных функций. Тогда вам просто нужно заполнить указатели на функции-члены в массиве.

0 голосов
/ 07 сентября 2011

Вы можете использовать функциональные объекты.

Например, Boost.Signal или из C ++ 0x / TR1

...