开发者

Array of functions with different signatures

I have this classes:

class Foo
{
    ...
};

class Foo1 : public Foo
{
    ...
};

...

class FooN : public Foo
{
    ...
};

Is it possible to have an array of functions with these kind of signatures:

void f1(Foo1*){}
...
void f开发者_高级运维N(FooN*){}

Is there any change if these functions are non static member functions instead of regular functions? I don't think this will change something.

Thanks!


EDIT alternative non-virtual-function-based solution here.

The type void(*)(Foo*) is not convertible to the type void(*)(Bar*) and for good reason.

You should make all your functions take an Interface* argument and all the FooN should derive from 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 ());

The implementations of f1, f2 can check at runtime if their argument is a particular implementation by using dynamic_cast and checking for nullptr. The only way to check at compile time is to make f1 and f2 take specific types and not put them in an anonymous array, but invoke them explicitly.


To answer the second part of your question -- yes it DOES matter if they're non-static member functions because the size of the pointer is not constant


You could use function objects. See the example below on how to do it yourselve. If you like the idea you should have a look at boost.signal/boost.bind and the c++ 0x counterparts.

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;
}


C++ is a statically typed language, and that includes the types of functions. At every line of code, the C++ compiler must be able to determine whether the function signature is valid and which function (or pointer) to call.

In order to do what you're talking about, you would need to be able to recover the type of the pointer at runtime, based on values put into the array at runtime. And polymorphism is the only type-related thing you can get at runtime. And even that only deals with the type of class. Exactly which function will be called is not up for debate.

The absolute best you can do is use something like an array of boost::variant. You can have a specific set of function prototypes stored in the variant, possibly using a boost::function. However, it would only be a bounded set, not any arbitrary function type. And calling them would be rather difficult, as you would first have to verify that the variant is indeed of the expected function type, then call it.

Another alternative is to use an array of boost::any. Except here, the types could be any function type. Again, calling it will require converting it to one of the expected function types. The problem is compounded since the function types could literally be anything. So you'll have to provide a fallback if it isn't one of the expected function types.

If the list of functions is small and compile-time determined, you could use a boost::tuple as a makeshift "array". However, you have to use template metaprogramming to iterate over them. Of course, if that were the case, you could just use a struct containing function pointers of the appropriate type.


You can do this in C++11 with Variadic Templates. Check my answer which is similar to what you want but with maps at: https://stackoverflow.com/a/33837343/1496826


You could use function objects.

For example Boost.Signal or the ones from C++0x / TR1


You could make the functions f1 through fN members of their particular argument classes, name them the same and use virtual dispatch to call the right functions. Then you would just have to fill pointers to the member functions into the array.


I found this workaround for this problem:

#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);
}


I would suggest using a std::tuple instead of a std::array or C-array. Using a std::tuple you can store elements of different types.


Here's a generic approach which is type-safe and forces client code to be correct.

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
}

If you don't want the overhead of the lookup in m_store (and/or want to avoid the void in Manager::Store) you can make Manager itself a template class, the downside is you now have to watch out for your static m_store definitions. This is OK if you know the clients will only use a given set of Function signatures.

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
}

The init function illustrates the crucial point I've been making in comments on other posts: if you know which types you're going to invoke a function with then you know which collection of functions to fetch from. There's no need to try to cram them all together and it only harms your type safety to do so.


What you want is covariant argument types. This is not supported in C++, because it breaks type safety. To better understand this, let's take a simple example:

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

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

Here we have a simple type hierarchy, as well as two functions taking a reference to respectively the base class and one of the derived class. Now, if what you ask for were allowed, we could do the following:

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

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

The language could implement some sort of runtime type verification, but this is not how C++ works.

However, the reverse conversion (contravariance) could be allowed without any issues (actually, C# delegates allow it), but is not possible in C++ for some reasons I am not aware of. Here is a sample of what it would allow:

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

So basically, what you are trying to achieve is not possible without resorting to forwarding functions that would check the type of the arguments before calling the original functions:

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

    if (instance) fN(instance);
    else throw type_exception();
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜