简体   繁体   English

具有不同签名的函数数组

[英]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 fN(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 . EDIT替代的基于非虚函数的解决方案在这里

The type void(*)(Foo*) is not convertible to the type void(*)(Bar*) and for good reason. void(*)(Foo*)类型不能转换为void(*)(Bar*)类型void(*)(Bar*) ,这是有充分理由的。

You should make all your functions take an Interface* argument and all the FooN should derive from Interface 您应该使所有函数都使用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 ());

The implementations of f1 , f2 can check at runtime if their argument is a particular implementation by using dynamic_cast and checking for nullptr . f1f2的实现可以在运行时检查它们的参数是否是特定的实现,方法是使用dynamic_cast并检查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. 在编译时检查的唯一方法是使f1f2采用特定类型而不是将它们放在匿名数组中,而是显式调用它们。


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. 如果你喜欢这个想法,你应该看看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;
}

You can do this in C++11 with Variadic Templates. 您可以使用Variadic模板在C ++ 11中执行此操作。 Check my answer which is similar to what you want but with maps at: https://stackoverflow.com/a/33837343/1496826 检查我的答案,这与您想要的相似,但地图位于: https//stackoverflow.com/a/33837343/1496826

C++ is a statically typed language, and that includes the types of functions. C ++是一种静态类型语言,包括函数类型。 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. 在每行代码中,C ++编译器必须能够确定函数签名是否有效以及调用哪个函数(或指针)。

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 . 你可以做的最好的事情是使用类似boost::variant的数组。 You can have a specific set of function prototypes stored in the variant, possibly using a boost::function . 您可以在变体中存储一组特定的函数原型,可能使用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 . 另一种方法是使用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". 如果函数列表很小并且确定了编译时间,则可以使用boost::tuple作为临时“数组”。 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 could use function objects. 您可以使用函数对象。

For example Boost.Signal or the ones from C++0x / TR1 例如Boost.Signal或来自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. 您可以创建其特定参数类的函数f1fN成员,将它们命名为相同并使用虚拟调度来调用正确的函数。 Then you would just have to fill pointers to the member functions into the array. 然后你只需要将指向成员函数的指针填充到数组中。

What you want is covariant argument types . 你想要的是协变参数类型 This is not supported in C++, because it breaks type safety. 这在C ++中不受支持,因为它打破了类型安全性。 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. 该语言可以实现某种运行时类型验证,但这不是C ++的工作方式。

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. 但是,可以允许反向转换(逆变)而没有任何问题(实际上,C#委托允许它),但由于某些我不知道的原因,在C ++中是不可能的。 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();
}

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. 我建议使用std::tuple而不是std::array或C-array。 Using a std::tuple you can store elements of different types. 使用std::tuple可以存储不同类型的元素。

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. 如果你不想在m_store查找的开销(和/或想要避免在Manager::Storevoid )你可以使Manager本身成为模板类,那么缺点是你现在必须注意你的静态m_store定义。 This is OK if you know the clients will only use a given set of Function signatures. 如果您知道客户端将仅使用给定的一组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
}

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. init函数说明了我在其他帖子的评论中所做的关键点:如果你知道哪些类型你将调用一个函数,那么你知道要从中获取哪些函数集合。 There's no need to try to cram them all together and it only harms your type safety to do so. 没有必要尝试将它们全部塞满,这只会损害您的类型安全性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM