【问题标题】:How to recover the type of a function pointer at runtime如何在运行时恢复函数指针的类型
【发布时间】:2014-05-05 22:46:37
【问题描述】:

在代码中,我在管理器类中注册了一个或多个函数指针。

在这个类中,我有一个将函数的参数类型映射到所述函数的映射。它可能看起来像这样:std::map< std::vector<std::type_index> , void*>

template<typename Ret, typename... Args>
void Register(Ret(*function)(Args...)) {
    void* v = (void*)function;
    // recursively build type vector and add to the map
}

在运行时,代码会收到带有任意数量参数的调用(来自外部脚本)。这些参数可以读取为原始数据类型或将在编译时指定的自定义类型。


对于脚本的每次调用,我都必须找出要调用的函数,然后调用它。前者很容易并且已经解决(在循环中用type_index 填充向量),但我想不出后者的解决方案。

我的第一种方法是在递归中使用可变参数模板,并为每个读取类型添加模板参数 - 但结果证明这是不可能的,因为模板是在编译时构建的,并且在运行时读取任意数量的参数。

但是,如果没有可变参数模板,我看不到任何实现这一目标的可能性。我考虑过boost::any 而不是void*,但我没有看到这将如何解决转换回原始类型的需要。我也想过使用std::function,但这将是一个模板类型,因此它不能存储在具有不同参数的函数的映射中。


(如果不清楚我在问什么,请考虑 LuaBindsregister overloaded functions 的可能性。我试图了解它是如何在那里实现的(没有可变参数模板,C++11 之前的),但是没用。)

【问题讨论】:

  • 也许吧? codereview.stackexchange.com/questions/46473/… 不确定这是否相似。我认为您可能正朝着编写解释器的方向前进。但不确定。
  • 顺便说一句,posix需要在void*和函数指针之间进行转换,但是标准C和C++不支持。
  • @Cheersandhth.-Alf 感谢您的意见,虽然it compiled without errors,但我不知道。一个类似(一致)的方法是存储一个指向模板类的虚拟基类的指针,该模板类存储实际的函数指针,尽管这最终会导致需要相同转换的相同问题。我根本不喜欢void*/casting 解决方案!这正是我能想到的。
  • 我认为您正在寻找类型擦除。 AFAIK 最简单的实现是使用包装函数模板,该模板将实际类型存储在其模板参数中:template&lt;class T&gt; void wrapper(void* p) { static_cast&lt;T*&gt;(p)-&gt;whatever(); } void(*f)(void*) = wrapper&lt;MyFirstType&gt;; f = wrapper&lt;MySecondType&gt;; 等。
  • 你不是在复制std::functionstd::bind的功能吗?

标签: c++ templates c++11 variadic-templates type-erasure


【解决方案1】:

假设您有某种vector 中的参数和一个已知函数(完全)。

你可以这样称呼。调用执行此操作的函数invoke

接下来,研究如何为template&lt;class... Args&gt; 执行此操作。增强invoke

所以你写了:

typedef std::vector<run_time_stuff> run_time_args;
template<class... Args>
void invoke( void(*func)(Args...), run_time_args rta )

此时。请注意,我们知道参数的类型。我并不是说上面的内容很容易写,但我相信你能弄明白。

现在我们总结一下:

template<class...Args>
std::function<void(run_time_args)> make_invoker(void(*func)(Args...)){
  return [func](run_time_args rta){
    invoke(func, rta);
  };
}

现在你存储std::function&lt;void(run_time_args)&gt; 而不是void*——调用者。当您将函数指针添加到您使用make_invoker 而不是强制转换为void* 的机制时。

基本上,在我们拥有类型信息的地方,我们存储了如何使用它。那么我们要在哪里使用它,我们就使用存储的代码!

invoke 是另一个问题。它可能会涉及索引技巧。

假设我们支持两种参数——doubleint。然后将运行时的参数作为我们的run_time_args 加载到std::vector&lt; boost::variant&lt;double, int&gt; &gt;

接下来,让我们扩展上面的invoke函数,在参数类型不匹配的情况下返回错误。

enum class invoke_result {
  everything_ok,
  error_parameter_count_mismatch,
  parameter_type_mismatch,
};
typedef boost::variant<int,double> c;
typedef std::vector<run_time_stuff> run_time_args;
template<class... Args>
invoke_result invoke( void(*func)(Args...), run_time_args rta );

现在是索引技巧的一些样板:

template<unsigned...Is>struct indexes{typedef indexes type;};
template<unsigned Max,unsigned...Is>struct make_indexes:make_indexes<Max-1, Max-1,Is...>{};
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};
template<unsigned Max>using make_indexes_t=typename make_indexes<Max>::type;

这样,我们就可以编写一个调用器了:

namespace helpers{
  template<unsigned...Is, class... Args>
  invoke_result invoke( indexes<Is...>, void(*func)(Args...), run_time_args rta ) {
    typedef void* pvoid;
    if (rta.size() < sizeof...(Is))
      return invoke_result::error_parameter_count_mismatch;
    pvoid check_array[] = { ((void*)boost::get<Args>( rta[Is] ))... };
    for( pvoid p : check_array )
      if (!p)
        return invoke_result::error_parameter_type_mismatch;
    func( (*boost::get<Args>(rts[Is]))... );
  }
}

template<class... Args>
invoke_result invoke( void(*func)(Args...), run_time_args rta ) {
  return helpers::invoke( make_indexes_t< sizeof...(Args) >{}, func, rta );
}

func 的参数与run_time_args 中传入的参数完全匹配时,这应该可以工作。

请注意,我在std::movestd::vector 周围都失败了。并且上面不支持隐式类型转换。而且我没有编译上面的任何代码,所以它可能有错别字。

【讨论】:

  • 你也可以使用boost::fusion::invoke :)
  • @Drax 不确定如何将其与运行时类型的参数集合一起使用。乍一看,它使用的前向序列似乎是编译时参数的集合。
  • 正如 OP 在他对@Steve Lorimer's answer 的评论中所说的那样:“参数类型在编译时是已知的”,只有它们的值是未知的,而这些类型正是您在 Args 中调用的模板参数你的invoke 函数模板。 boost::fusion 使用的序列是混合序列,编译时信息是类型,运行时信息是值,非常适合这个用例:)
  • 我不知道我是否理解正确的代码,但这不需要持有模板类型吗?如何以这种方式将具有不同签名的函数存储在地图中?
  • @AdamS ideone.com/B8fRw1invoke(带有存根函数,可能还有错误的索引等)。使用typedef lua_state* run_time_args。将std::function&lt; void(run_time_args) &gt; 存储在您的地图中(而不是您的void*)。使用make_invoker(如上所述)将函数指针转换为该指针。我错过了什么吗?
【解决方案2】:

几周前我在搞乱可变参数模板,想出了一个可能对您有所帮助的解决方案。

DELEGATE.H

template <typename ReturnType, typename ...Args>
class BaseDelegate
{
public:
    BaseDelegate()
        : m_delegate(nullptr)
    {

    }

    virtual ReturnType Call(Args... args) = 0;
    BaseDelegate* m_delegate;
};

template <typename ReturnType = void, typename ...Args>
class Delegate : public BaseDelegate<ReturnType, Args...>
{
public:
    template <typename ClassType>
    class Callee : public BaseDelegate
    {
    public:
        typedef ReturnType (ClassType::*FncPtr)(Args...);
    public:
        Callee(ClassType* type, FncPtr function)
            : m_type(type)
            , m_function(function)
        {

        }

        ~Callee()
        {

        }

        ReturnType Call(Args... args)
        {
            return (m_type->*m_function)(args...);
        }

    protected:
        ClassType* m_type;
        FncPtr m_function;
    };

public:
    template<typename T>
    void RegisterCallback(T* type, ReturnType (T::*function)(Args...))
    {
        m_delegate = new Callee<T>(type, function);
    }

    ReturnType Call(Args... args)
    {
        return m_delegate->Call(args...);
    }
};

MAIN.CPP

class Foo
{
public:
    int Method(int iVal)
    {
        return iVal * 2;
    }
};

int main(int argc, const char* args)
{
    Foo foo;
    typedef Delegate<int, int> MyDelegate;

    MyDelegate m_delegate;
    m_delegate.RegisterCallback(&foo, &Foo::Method);

    int retVal = m_delegate.Call(10);

    return 0;
}

【讨论】:

  • 这对 OP 的多重签名问题有何帮助?
【解决方案3】:

不确定您的要求是否允许这样做,但您可能只使用std::functionstd::bind

以下解决方案做出以下假设:

  • 您知道要调用的函数及其参数
  • 函数可以有任意签名和任意数量的参数
  • 您希望使用类型擦除来存储这些函数和参数,并在以后调用它们

这是一个工作示例:

#include <iostream>
#include <functional>
#include <list>

// list of all bound functions
std::list<std::function<void()>> funcs;

// add a function and its arguments to the list
template<typename Ret, typename... Args, typename... UArgs>
void Register(Ret(*Func)(Args...), UArgs... args)
{
    funcs.push_back(std::bind(Func, args...));
}

// call all the bound functions
void CallAll()
{
    for (auto& f : funcs)
        f();
}

////////////////////////////
// some example functions
////////////////////////////

void foo(int i, double d)
{
    std::cout << __func__ << "(" << i << ", " << d << ")" << std::endl;
}

void bar(int i, double d, char c, std::string s)
{
    std::cout << __func__ << "(" << i << ", " << d << ", " << c << ", " << s << ")" << std::endl;
}

int main()
{
    Register(&foo, 1, 2);
    Register(&bar, 7, 3.14, 'c', "Hello world");

    CallAll();
}

【讨论】:

  • 这看起来不错,bind 似乎是朝着正确方向迈出的一步。但是,只有参数类型在编译时是已知的;参数不是 - 它们是在运行时给出的(函数可以用不同的参数多次调用),所以它们不能被绑定到Register
  • @AdamS - 所有函数都具有相同的签名吗? (即:相同数量的参数,相同类型的参数)
  • 不,这就是我面临的问题。可以注册具有不同签名的任意数量的功能。该脚本将调用function(100, 10, 'foo'),并且根据这些信息,我必须调用其中一个函数(适合签名的那个)。请参阅我原始问题中的链接以获取一个很好的示例。
猜你喜欢
  • 2021-10-07
  • 1970-01-01
  • 2012-10-05
  • 2015-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-09
相关资源
最近更新 更多