简体   繁体   English

我可以合法地将成员函数指针转换为函数指针吗?

[英]Can I legally cast a member function pointer to a function pointer?

I've inherited some C++ code and I've been tasked with getting rid of warnings.我继承了一些 C++ 代码,并且我的任务是消除警告。

Here we have a member function pointer being cast to a function pointer.这里我们有一个成员函数指针被强制转换为函数指针。 I understand that member function pointers are "different" from function pointers, in that there is an implicit 'this' parameter involved under the hood.我知道成员函数指针与函数指针“不同”,因为在引擎盖下有一个隐式的“this”参数。 However my predecessor appears to have made explicit use of this fact, by casting from a member function pointer to a function pointer with an additional first parameter inserted.然而,我的前任似乎已经明确利用了这一事实,将成员函数指针转换为插入了附加第一个参数的函数指针。

My Questions are:我的问题是:

A) Can I get rid of the compiler warning? A) 我可以摆脱编译器警告吗?

B) To what extent is this code guaranteed to work? B)这段代码在多大程度上保证工作?

I've cut it down to a small main.cpp for the purposes of this question:出于这个问题的目的,我已将其缩减为一个小的 main.cpp:

#define GENERIC_FUNC_TYPE   void(*)(void)
#define FUNC_TYPE       int(*)(void *)

class MyClass
{
public:
    MyClass(int a) : memberA(a) {}
    int myMemberFunc()
    {
        return memberA;
    }

private:
    int memberA;
};

int main(int argc, char*argv[])
{
    int (MyClass::* memberFunc) () = &MyClass::myMemberFunc;
    MyClass myObject(1);
    std::cout << (myObject.*memberFunc)() << std::endl;
    // All good so far

    // Now get naughty, store it away in a very basic fn ptr
    void(*myStoredFunction)(void) = (GENERIC_FUNC_TYPE)memberFunc;  // Compiler warning

    // Reinterpret the fn pointer as a pointer to fn, with an extra object parameter
    int (*myExtractedFunction)(void*) = (FUNC_TYPE)myStoredFunction;

    // Call it
    std::cout << myExtractedFunction(&myObject) << std::endl;
}

The code compiles with one warning under g++, and as intended outputs two 1's:代码在 g++ 下编译时出现一个警告,并按预期输出两个 1:

main.cpp: In function ‘int main(int, char**)’:
main.cpp:27:53: warning: converting from ‘int (MyClass::*)()’ to ‘void (*)()’ [-Wpmf-conversions]
  void(*myStoredFunction)(void) = (GENERIC_FUNC_TYPE)memberFunc; // Compiler warning
                                                     ^

IMHO this code is making assumptions about the underlying mechanisms of the compiler.恕我直言,这段代码对编译器的底层机制做出了假设。 Or maybe these assumptions are valid for all C++ compilers - Can anyone help?或者这些假设对所有 C++ 编译器都有效——有人可以帮忙吗?

(In the actual code we're storing a whole bunch of function pointers by name in a map. These functions all have different signatures, which is why they are all cast to the same signature void(*)(void). This is analogous to the myStoredFunction above. They are then cast to the individual signatures at the point of calling, analogous to myExtractedFunction above.) (在实际代码中,我们在映射中按名称存储了一大堆函数指针。这些函数都有不同的签名,这就是为什么它们都被强制转换为相同的签名 void(*)(void)。这是类似的到上面的 myStoredFunction。然后在调用时将它们转换为单个签名,类似于上面的 myExtractedFunction。)

How about create functions which avoid the cast entirely:如何创建完全避免强制转换的函数:

template <typename C, void (C::*M)()>
void AsFunc(void* p)
{
    (static_cast<C*>(p)->*M)();
}

then然后

void(*myStoredFunction)(void) = &AsFunc<MyClass, &MyClass::myMemberFunc>;

In C++17, with some traits, you might even have template <auto *M> void AsFunc(void* p) and void(*myStoredFunction)(void) = &AsFunc<&MyClass::myMemberFunc>;在 C++17 中,有一些特性,你甚至可能有template <auto *M> void AsFunc(void* p)void(*myStoredFunction)(void) = &AsFunc<&MyClass::myMemberFunc>;

To answer the question in the title, no, you can't legally cast a pointer-to-member-function to a pointer-to-function.要回答标题中的问题,不,您不能合法地将指向成员函数的指针转换为指向函数的指针。 Presumably, that's what the "Compiler warning" on the line with that cast said.据推测,这就是该演员阵容上的“编译器警告”所说的。

A conforming compiler is required to issue a diagnostic when confronted with ill-formed code (that's a bit oversimplified), and this one did.当遇到格式错误的代码(这有点过于简化)时,需要符合标准的编译器发出诊断信息,而这个人做到了。 It gave a warning.它发出了警告。 Having done that, the compiler is free to do something implementation-specific, which it seems to have done: it compiled the code into something that does what you were hoping for.这样做之后,编译器可以自由地做一些特定于实现的事情,它似乎已经完成了:它将代码编译成可以做你希望做的事情。

Compilers are free to represent pointers to member functions in any way that works, and for non-virtual functions, that could be just a "normal" pointer to function.编译器可以以任何有效的方式自由地表示指向成员函数的指针,对于非虚拟函数,这可能只是指向函数的“正常”指针。 But try that with a virtual function;但是用虚函数试试; I'll bet the consequences are more harsh.我敢打赌后果更严重。

Since you apparently need to call a function by name on some "untyped" object ( void* ) while passing in a number of arguments that differ by function, you need some kind of multiple-dispatch.由于您显然需要在某些“无类型”对象 ( void* ) 上按名称调用函数,同时传入许多因函数而异的参数,因此您需要某种多分派。 A possible solution is:一个可能的解决方案是:

#include <string>
#include <iostream>
#include <stdexcept>
#include <functional>
#include <utility>
#include <map>

template <typename Subj>
using FunctionMap = std::map<std::string, std::function<void (Subj&, const std::string&)>>;

class AbstractBaseSubject {
    public:
        virtual void invoke (const std::string& fName, const std::string& arg) = 0;
};

template <typename Class>
class BaseSubject : public AbstractBaseSubject {
    public:
        virtual void invoke (const std::string& fName, const std::string& arg) {
            const FunctionMap<Class>& m = Class::functionMap;

            auto iter = m.find (fName);
            if (iter == m.end ())
                throw std::invalid_argument ("Unknown function \"" + fName + "\"");

            iter->second (*static_cast<Class*> (this), arg);
        }
};

class Cat : public BaseSubject<Cat> {
    public:
        Cat (const std::string& name) : name(name) {}
        void meow (const std::string& arg) {
            std::cout << "Cat(" << name << "): meow (" << arg << ")\n";
        }

        static const FunctionMap<Cat> functionMap;
    private:
        std::string name;
};

const FunctionMap<Cat> Cat::functionMap = {
    { "meow", [] (Cat& cat, const std::string& arg) { cat.meow (arg);  } }
};

class Dog : public BaseSubject<Dog> {
    public:
        Dog (int age) : age(age) {}
        void bark (float arg) {
            std::cout << "Dog(" << age << "): bark (" << arg << ")\n";
        }

        static const FunctionMap<Dog> functionMap;
    private:
        int age;
};

const FunctionMap<Dog> Dog::functionMap = {
    { "bark", [] (Dog& dog, const std::string& arg) { dog.bark (std::stof (arg));  }}
};

int main () {
    Cat cat ("Mr. Snuggles");
    Dog dog (7);

    AbstractBaseSubject& abstractDog = dog;     // Just to demonstrate that the calls work from the base class.
    AbstractBaseSubject& abstractCat = cat;

    abstractCat.invoke ("meow", "Please feed me");
    abstractDog.invoke ("bark", "3.14");

    try {
        abstractCat.invoke ("bark", "3.14");
    } catch (const std::invalid_argument& ex) {
        std::cerr << ex.what () << std::endl;
    }
    try {
        abstractCat.invoke ("quack", "3.14");
    } catch (const std::invalid_argument& ex) {
        std::cerr << ex.what () << std::endl;
    }
    try {
        abstractDog.invoke ("bark", "This is not a number");
    } catch (const std::invalid_argument& ex) {
        std::cerr << ex.what () << std::endl;
    }
}

Here, all classes with functions to be called this way need to derive from BaseSubject (which is a CRTP ).在这里,所有具有以这种方式调用的函数的类都需要从BaseSubject (这是一个CRTP )派生。 These classes (here: Cat and Dog , let's call them "subjects") have different functions with different arguments ( bark and meow - of course more than one function per subject is possible).这些类(此处: CatDog ,我们称它们为“主体”)具有不同参数的不同函数( barkmeow - 当然每个主体可能有多个函数)。 Each subject has its own map of string-to-function.每个主题都有自己的字符串到函数map These functions are not function pointers, but std::function<void (SubjectType&,const std::string&)> instances.这些函数不是函数指针,而是std::function<void (SubjectType&,const std::string&)>实例。 Each of those should call the respective member function of the object, passing in the needed arguments.每个都应该调用对象的相应成员函数,传入所需的参数。 The arguments need to come from some kind of generic data representation - here, I chose a simple std::string .参数需要来自某种通用数据表示 - 在这里,我选择了一个简单的std::string It might be a JSON or XML object depending on where your data comes from.它可能是 JSON 或 XML 对象,具体取决于您的数据来自何处。 The std::function instances need to deserialize the data and pass it as arguments. std::function实例需要反序列化数据并将其作为参数传递。 The map is created as a static variable in each subject class, where the std::function instances are populated with lambdas. map在每个主题类中创建为static变量,其中std::function实例填充了 lambda。 The BaseSubject class looks up the function instance and calls it. BaseSubject类查找function实例并调用它。 Since the subject class should always directly derive from BaseSubject<Subject> , pointers of type BaseSubject<Subject>* may be directly and safely cast to Subject* .由于主题类应始终直接从BaseSubject<Subject>派生, BaseSubject<Subject> BaseSubject<Subject>*类型的指针可以直接且安全地转换为Subject*

Note that there is no unsafe cast at all - it is all handled by virtual functions.请注意,根本没有不安全的强制转换——它都是由虚函数处理的。 Therefore, this should be perfectly portable.因此,这应该是完全便携的。 Having one map per subject class is typing-intensive, but allows you to have identically-named functions in different classes.每个学科类有一个map是打字密集型的,但允许您在不同的类中有相同命名的函数。 Since some kind of data-unpacking for each function individually is necessary anyways, we have individual unpacking-lambdas inside the map .由于无论如何都需要为每个函数单独进行某种数据解包,因此我们在map有单独的解包 lambdas。

If a function's arguments are just the abstract data structure, ie const std::string& , we could leave the lambdas out and just do:如果函数的参数只是抽象数据结构,即const std::string& ,我们可以将 lambdas 排除在外,只需执行以下操作:

const FunctionMap<Cat> Cat::functionMap = {
    { "meow", &Cat::meow }
};

Which works by way of std::function s magic (passing this via the 1st argument), which, in contrast to function pointers, is well-defined and allowed.它通过std::function的魔法(通过第一个参数传递this )起作用,与函数指针相比​​,它是明确定义和允许的。 This would be particularly useful if all functions have the same signature.如果所有函数都具有相同的签名,这将特别有用。 In fact, we could then even leave out the std::function and plug in Jarod42's suggestion.事实上,我们甚至可以省略std::function并插入 Jarod42 的建议。

PS: Just for fun, here's an example where casting a member-function-pointer to an function-pointer fails: PS:只是为了好玩,这是一个将成员函数指针转换为函数指针失败的示例:

#include <iostream>

struct A {
    char x;
    A () : x('A') {}
    void foo () {
        std::cout << "A::foo() x=" << x << std::endl;
    }
};

struct B {
    char x;
    B () : x('B') {}
    void foo () {
        std::cout << "B::foo() x=" << x << std::endl;
    }
};

struct X : A, B {
};

int main () {
    void (B::*memPtr) () = &B::foo;
    void (*funPtr) (X*) = reinterpret_cast<void (*)(X*)> (memPtr);  // Illegal!

    X x;
    (x.*memPtr) ();
    funPtr (&x);
}

On my machine, this prints:在我的机器上,这会打印:

B::foo() x=B
B::foo() x=A

The B class shouldn't be able to print "x=A"! B类应该不能打印“x=A”! This happens because member-function pointers carry an extra offset that is added to this before the call, in case multiple inheritance comes into play.发生这种情况是因为成员函数指针携带一个额外的偏移量,在调用之前添加到this ,以防多重继承发挥作用。 Casting loses this offset.铸造失去了这个偏移。 So, when calling the casted function pointer, this automatically refers to the first base object, while B is the second, printing the wrong value.因此,当调用强制转换的函数指针时, this自动引用第一个基对象,而B是第二个,打印错误的值。

PPS: For even more fun: If we plug in Jarod42's suggestion: PPS:为了更有趣:如果我们插入 Jarod42 的建议:

template <typename C, void (C::*M)(), typename Obj>
void AsFunc (Obj* p) {
    (p->*M)();
}

int main () {
    void (*funPtr) (X*) = AsFunc<B, &B::foo, X>;

    X x;
    funPtr (&x);
}

the program correctly prints:程序正确打印:

B::foo() x=B

If we look at the disassembly of AsFunc , we see:如果我们查看AsFunc的反汇编,我们会看到:

c90 <void AsFunc<B, &B::foo, X>(X*)>:
 c90:   48 83 c7 01             add    $0x1,%rdi
 c94:   e9 07 ff ff ff          jmpq   ba0 <B::foo()>

The compiler automatically generated code that adds 1 to the this pointer, such that B::foo is called with this pointing to the B base class of X .编译器自动生成将this指针加1代码,这样调用B::foothis指向XB基类。 To make this happen in the AsFunc function (opposed to buried within main ), I introduced the Obj template parameter which lets the p argument be of the derived type X such that AsFunc has to do the adding.为了在AsFunc函数中实现这一点(与埋在main相反),我引入了Obj模板参数,它让p参数是派生类型X这样AsFunc必须进行添加。

A) Can I get rid of the compiler warning? A) 我可以摆脱编译器警告吗?

Yes - wrap the member function in a call from a static function是 - 将成员函数包装在来自静态函数的调用中

(This is a low-tech variant of @Jarod42's template based answer) (这是@Jarod42 基于模板的答案的低技术变体)

B) To what extent is this code guaranteed to work? B)这段代码在多大程度上保证工作?

It's not (summarizing @Pete Becker's answer).不是(总结@Pete Becker 的回答)。 Until you get rid of the warning.直到你摆脱警告。

Here's the jist of what we went with.这是我们的目标。 We kept it simple to minimize disruption to the code.我们保持简单以尽量减少对代码的干扰。 We avoided advanced C++ features to maximize the number of people who can work on the code.我们避免使用高级 C++ 功能,以最大限度地增加可以处理代码的人数。

#include <iostream>

class MyClass
{
public:
    MyClass(int a) : memberA(a) {}
    static int myMemberFuncStatic(MyClass *obj)
    {
        return obj->myMemberFunc();
    }   
    int myMemberFunc()
    {
        return memberA;
    }

private:
    int memberA;
};

typedef void(*GENERIC_FUNC_TYPE)(void);
typedef int(*FUNC_TYPE)(MyClass *);

int main(int argc, char*argv[])
{
    int (* staticFunc) (MyClass *) = &MyClass::myMemberFuncStatic;
    MyClass myObject(1);
    std::cout << staticFunc(&myObject) << std::endl;
    // All good so far

    // This is actually legal, for non-member functions (like static functions)
    GENERIC_FUNC_TYPE myStoredFunction = reinterpret_cast<GENERIC_FUNC_TYPE> (staticFunc);  // No compiler warning

    // Reinterpret the fn pointer as the static function
    int (*myExtractedFunction)(MyClass*) = (FUNC_TYPE)myStoredFunction;

    // Call it
    std::cout << myExtractedFunction(&myObject) << std::endl;
}

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

相关问题 我可以合法地将指向结构成员的指针转换为指向该结构的指针吗? - Can I legally cast a pointer to a struct member to a pointer to the struct? 如何合法地将 function 指针转换为方法指针? - How to legally cast function pointer to method pointer? 将指向成员函数的指针转换为普通指针 - Cast pointer to member function to normal pointer 将派生类中的成员函数的指针强制转换为抽象成员函数的指针 - cast a pointer to member function in derived class to a pointer to abstract member function 将成员函数的指针转换为C函数的指针是一种好习惯吗 - Is it good practice to cast a pointer to a member function as a pointer to a C function 如何将这个指针与成员函数的指针一起使用 - How can I use this pointer with pointer to member function reinterpret_cast 成员函数指针指向 void(*&amp;)() - reinterpret_cast member function pointer to void(*&)() 将指向成员函数的指针转换为intptr_t - Cast pointer to member function to intptr_t 重载成员函数指针的演员转换助手 - cast helper for overloaded member function pointer 为什么我不能将指向Derived类成员函数的指针强制转换为类Base? - why can't I cast a pointer to Derived class member function to the same but of class Base?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM