【问题标题】:Array of functions with different signatures具有不同签名的函数数组
【发布时间】:2011-11-11 23:12:10
【问题描述】:

我有这些课程:

class Foo
{
    ...
};

class Foo1 : public Foo
{
    ...
};

...

class FooN : public Foo
{
    ...
};

是否有可能拥有具有这些签名的函数数组:

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

如果这些函数是非静态成员函数而不是常规函数,有什么变化吗?我认为这不会改变什么。

谢谢!

【问题讨论】:

  • 你可以使用多态性和指向 Foo 的指针吗?您不能拥有指向具有不同签名的函数的指针数组。您可以让它们都具有 Foo* 或都具有 void*(我建议使用 Foo*)。
  • 如果我有一个指向 Foo* 的指针,我需要在 fK 中对 FooK 进行强制转换,我想避免这种情况
  • 你要么必须想办法多态地做到这一点(正如斯普拉夫在我的第一条评论中详细阐述的那样),要么有一些有趣的指针转换。
  • 你的目标不兼容。如果您想避免dynamic_cast,那么您必须将不同的函数类型塞入同一个容器中。在您的设计中将这些类型分开。
  • 你打算用这样一个数组做什么?数组通常用于对元素的统一访问。但是,您没有关于打包到数组中的函数的特定签名的运行时信息,因此,您甚至无法正确调用 f[0](..), f[1](..) 函数不知道参数类型。或者您的问题中缺少一些其他信息?

标签: c++ function-pointers c++11 method-signature


【解决方案1】:

您可以在 C++11 中使用可变参数模板执行此操作。检查我的答案,这与您想要的类似,但地图位于:https://stackoverflow.com/a/33837343/1496826

【讨论】:

    【解决方案2】:

    编辑基于非虚拟功能的替代解决方案here

    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 ());
    

    f1f2 的实现可以通过使用dynamic_cast 并检查nullptr 在运行时检查它们的参数是否是特定实现。在编译时进行检查的唯一方法是使 f1f2 采用特定类型并且将它们放入匿名数组中,而是显式调用它们。


    回答你问题的第二部分——是的,如果它们是非静态成员函数,这很重要,因为the size of the pointer is not constant

    【讨论】:

    • 我知道我可以创建一个具有相同签名的函数数组,我想知道是否可以使用一些技巧或其他方法创建一个具有不同签名的函数数组:)。只需将 Interface 替换为 base Foo ,您将拥有与我的问题相同的代码:)
    • void f(Foo*) 替换void f(Foo1*),你就会得到我的答案:-) 数组 包含相同类型的对象。如果您可以证明所有调用都将包含特定的运行时类型,那么明智的做法是为每个特定类型设置一个数组。
    【解决方案3】:

    你想要的是covariant argument types。这在 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();
    }
    

    【讨论】:

      【解决方案4】:

      这是一种类型安全且强制客户端代码正确的通用方法。

      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 中查找开销(和/或想避免Manager::Store 中的void),您可以将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 函数说明了我在其他帖子中在 cmets 中提出的关键点:如果您知道要使用哪些类型调用函数,那么您知道 要从哪个函数集合中获取。没有需要尝试将它们全部塞在一起,这样做只会损害您的类型安全。

      【讨论】:

        【解决方案5】:

        我建议使用std::tuple 而不是std::array 或C 数组。使用std::tuple,您可以存储不同类型的元素。

        【讨论】:

          【解决方案6】:

          你可以使用函数对象。请参阅下面的示例,了解如何自己做。如果你喜欢这个想法,你应该看看 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;
          }
          

          【讨论】:

          • 虽然它们都愉快地进入了数组,但你不能再调用任何带有FuncObj绑定之外的参数的函数。 -1 忽略了要点,重新实现 function/bind,即使你知道它们,以及在示例中使用 非常 狡猾的对象所有权逻辑。
          • 这真的不是积分。这里的大多数答案都不是类型安全的,人们似乎并不关心这让我很苦恼。这是类型安全的,但没有用,因为它需要绑定参数。顺便说一句,智能指针与所有权问题无关。
          【解决方案7】:

          我找到了解决这个问题的方法:

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

          【讨论】:

          • 你不必要地抛弃了类型安全。有类型安全的无开销方法可以做到这一点。
          • @spraff 我不需要类型安全,我只需要能够从向量调用具有不同签名的函数,而无需在函数内部进行任何强制转换。这是我的问题,这就是它的答案。如果我已经有了向量之外的类型信息,为什么还要在函数内进行另一个转换?这个实现在一个库中,它的用户只需要知道如果他在管理器中注册一个带有签名的函数,该函数将使用正确的参数调用,他不需要强制转换它。一个库必须有一个简单的界面,因为它是用户想要的
          • 确实需要类型安全。当您向管理器注册特定签名的功能时,管理器应将该功能存储在某处。当您向管理器注册一个不同签名的函数时,管理器应该将该函数存储在其他地方,而不是在同一个数组/向量中。
          【解决方案8】:

          C++ 是一种静态类型语言,其中包括函数类型。在每一行代码中,C++ 编译器必须能够确定函数签名是否有效以及调用哪个函数(或指针)。

          为了执行您所说的操作,您需要能够在运行时根据在运行时放入数组的值来恢复指针的类型。多态性是您在运行时可以获得的唯一与类型相关的东西。甚至 that 也只处理类的类型。具体调用哪个函数没有争议。

          您能做的最好的事情就是使用boost::variant 之类的数组。您可以将一组特定的函数原型存储在变体中,可能使用boost::function。但是,它只是一个有界集合,而不是任何任意函数类型。而且调用它们会相当困难,因为您首先必须验证变体确实是预期的函数类型,然后调用它。

          另一种选择是使用boost::any 的数组。除了这里,类型可以是任何函数类型。同样,调用它需要将其转换为预期的函数类型之一。由于函数类型实际上可以是任何东西,因此问题变得更加复杂。因此,如果它不是预期的函数类型之一,则必须提供后备。

          如果函数列表很小并且编译时确定,您可以使用boost::tuple 作为临时“数组”。但是,您必须使用模板元编程来迭代它们。当然,如果是这种情况,您可以只使用包含适当类型的函数指针的结构。

          【讨论】:

          • -1 有什么原因吗?
          【解决方案9】:

          您可以使函数f1fN 成为其特定参数类的成员,将它们命名为相同并使用虚拟调度来调用正确的函数。然后,您只需将指向成员函数的指针填充到数组中。

          【讨论】:

            【解决方案10】:

            你可以使用函数对象。

            例如 Boost.Signal 或来自 C++0x / TR1 的那些

            【讨论】:

            • 不,std::function 的完整类型例如是std::function&lt;void(Foo*)&gt;。这个多态函数包装器完全正确地与等效的普通函数具有精确的类型对应关系。
            • @spraff 这使得它们非常适合在函数对象数组中使用?
            • std::funcion&lt;void(Foo*)&gt; 不能与 std::funcion&lt;void(Bar*)&gt; 在同一个数组中,因为它们是不同类型的对象。
            • @spraff with std::bind 你可以
            • 我认为您误解了 std::bind 的用途。我不认为它 can 以这种方式将 Foo* 映射到 Bar*(它将 特定 Foo*Bar* 绑定到函数,产生std::function&lt;void()&gt;) 但即使它可以做到这一点,也不是必需的。
            猜你喜欢
            • 2019-12-19
            • 2011-05-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-08-18
            相关资源
            最近更新 更多