C++尝试从std::function中获取函数地址

18

我正在尝试从std :: function中查找函数的地址。

第一种解决方案是:

size_t getAddress(std::function<void (void)> function) {
    typedef void (fnType)(void);
    fnType ** fnPointer = function.target<fnType *>();
    return (size_t) *fnPointer;
}

但这仅适用于带有(void())签名的函数,因为我需要的函数需要具有(void(Type &))签名,我尝试了做如下的修改。
template<typename T>
size_t getAddress(std::function<void (T &)> function) {
    typedef void (fnType)(T &);
    fnType ** fnPointer = function.target<fnType *>();
    return (size_t) *fnPointer;
}

我收到了“错误 - 预期'('用于函数风格的转换或类型构造”的提示。

更新:有没有办法捕获成员类地址?对于类成员,我正在使用:

template<typename Clazz, typename Return, typename ...Arguments>
size_t getMemberAddress(std::function<Return (Clazz::*)(Arguments...)> & executor) {
    typedef Return (Clazz::*fnType)(Arguments...);
    fnType ** fnPointer = executor.template target<fnType *>();
    if (fnPointer != nullptr) {
        return (size_t) * fnPointer;
    }
    return 0;
}

更新:为了捕获lambda,我正在使用
template <typename Function>
struct function_traits
    : public function_traits<decltype(&Function::operator())> {
};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
    typedef ReturnType (*pointer)(Args...);
    typedef std::function<ReturnType(Args...)> function;
};

template <typename Function>
typename function_traits<Function>::function
to_function (Function & lambda) {
    return static_cast<typename function_traits<Function>::function>(lambda);
}

template <typename Lambda>
size_t getAddress(Lambda lambda) {
    auto function = new decltype(to_function(lambda))(to_function(lambda));
    void * func = static_cast<void *>(function);
    return (size_t)func;
}

std::cout << getAddress([] { std::cout << "Hello" << std::endl;}) << std::endl;

2
看起来工作正常。您可能需要包括一个完整的示例,包括用法。一些显示实际失败的东西。 - Captain Obvlious
另外,您假设(在未定义的行为的痛苦下)您std::function中的所有内容都是函数指针,这并不是很明智,因为如果它们是函数指针,您可以使用函数指针类型而不必遇到所有这些麻烦。 - Caleth
2个回答

29

在调用目标时,您需要使用template关键字:

#include <functional>
#include <iostream>

template<typename T>
size_t getAddress(std::function<void (T &)> f) {
    typedef void (fnType)(T &);
    fnType ** fnPointer = f.template target<fnType*>();
    return (size_t) *fnPointer;
}

void foo(int& a) {
    a = 0;
}

int main() {
    std::function<void(int&)> f = &foo;
    std::cout << (size_t)&foo << std::endl << getAddress(f) << std::endl;
    return 0;
}

提示:当你在处理C++语法时遇到问题,建议使用clang++编译你的代码。如果你在编写代码时玩弄语法,它通常会指向正确的方向来解决错误(当它能够理解你在做什么时)。

我还建议您使用可变参数模板使您的函数更加通用:

template<typename T, typename... U>
size_t getAddress(std::function<T(U...)> f) {
    typedef T(fnType)(U...);
    fnType ** fnPointer = f.template target<fnType*>();
    return (size_t) *fnPointer;
}

我目前正在使用CLang 3.4 :D - Agustin Alvarez
1
在这种情况下,如果你添加 typedef fnType* fnTypeP; 并且将函数调用更改为 target<fnTypeP>,clang 将会告诉你在哪里添加 template。如果你让解析器的工作足够容易,它就可以弄清楚你想要做什么 :) - Robert Mason
等等,你能把std::function简单地转换成一个函数指针吗?这对于有状态的函数是否有效?那是如何工作的? - Mooing Duck
8
不是所有情况都可以这样做。例如,如果将该函数绑定到一个带有捕获的 Lambda 中,target() 将返回空指针,并且函数将引起段错误。请谨慎使用。 - Robert Mason
是的,我刚试了一下,返回了空指针:(。可以捕获Lambda地址和&Class::function吗? - Agustin Alvarez
显示剩余2条评论

9

std::function只是一个对象,虽然它伪装成不是对象。因此,我们可以取得这个对象的地址,这在拷贝等操作时是不变的。

要获取指针,我们需要进行一些强制转换。例如,对于函数f1,我们可以通过以下方式打印隐式指针:

std::cout << "f1: " << *(long *)(char *)&f1 << std::endl;

这段代码的作用是:
- 获取f1的地址。f1本身是一个指向函数对象的指针,尽管它被伪装成了原始类型。 - 所以这个地址是指向指向函数的指针的指针。 - 我们需要的是底层函数指针。 - 将指针转换为指向char的指针。 - 然后转换为指向long的指针。 - 现在,假设指针大小等于long,我们可以解引用这个指向long的指针,并得到与函数对象相关联的底层地址。
给定两个std::functions f1和f2,我们可以做如下操作:
std::cout << "f1: " << *(long *)(char *)&f1 << std::endl;
std::cout << "f2: " << *(long *)(char *)&f2 << std::endl;

std::function<void()> f1copy = f1;
std::cout << "\nafter copy f1 into f1copy:" << std::endl;
std::cout << "addresses of f1 and f1copy differ: " << &f1 << " " << &f1copy << std::endl;
std::cout << "but underlying pointers identical: " <<
    *(long *)(char *)&f1 << " " << *(long *)(char *)(&f1copy) << std::endl;

std::cout << "\n after assign f2 to f1copy" << std::endl;
f1copy = f2;
std::cout << "now the underlying pointer of f1copy matches f2's:" << std::endl;
std::cout <<
    *(long *)(char *)&f2 << " " << *(long *)(char *)&f1copy << std::endl;

示例输出:

f1: 4439003784
f2: 4439003912

after copy f1 into f1copy:
addresses of f1 and f1copy differ: 0x7fff572ab970 0x7fff572ab910
but underlying pointers identical: 4439003784 4439003784

 after assign f2 to f1copy
now the underlying pointer of f1copy matches f2's:
4439003912 4439003912

1
它有效,谢谢!但我必须承认,这可能是不安全的,例如如果编译器使用不同的实现,那么第一个成员可能不是指针,而只是填充或类似的东西(尽管我不认为会发生这种情况)。但仍然是个好答案。 - XvXLuka222

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接