我继承了一些C ++代码,并且承担了摆脱警告的任务。
在这里,我们有一个成员函数指针被转换为一个函数指针。 我知道成员函数指针与函数指针“不同”,因为在内部有一个隐式的“ this”参数。但是,我的前任似乎已经明确地利用了这一事实,将其从成员函数指针转换为插入了附加第一个参数的函数指针。
我的问题是:
A)我可以摆脱编译器警告吗?
B)此代码在什么程度上可以保证工作?
出于这个问题的目的,我将其缩减为一个小的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;
}
该代码在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
^
恕我直言,这段代码对编译器的基本机制进行了假设。也许这些假设对所有C ++编译器都有效-有人可以帮忙吗?
(在实际的代码中,我们按名称在地图中存储了一堆函数指针。这些函数都具有不同的签名,这就是为什么它们都被强制转换为相同的签名void(*)(void)的原因。这类似于上面的myStoredFunction,然后在调用时将它们强制转换为单个签名,类似于上面的myExtractedFunction。)
答案 0 :(得分:6)
如何创建完全避免强制转换的函数:
UIApplication.Main(args, null, "AppDelegate");
然后
template <typename C, void (C::*M)()>
void AsFunc(void* p)
{
(static_cast<C*>(p)->*M)();
}
在C ++ 17中,具有某些特征,您甚至可能拥有void(*myStoredFunction)(void) = &AsFunc<MyClass, &MyClass::myMemberFunc>;
和template <auto *M>
void AsFunc(void* p)
答案 1 :(得分:3)
要回答标题中的问题,不,您不能合法地将指向成员函数的指针转换为指向函数的指针。大概就是那个强制转换行上的“编译器警告”。
当遇到格式错误的代码(有点过分简化)时,需要一个合格的编译器发出诊断,而这个诊断确实如此。它发出了警告。完成此操作后,编译器可以自由地执行特定于实现的操作,这似乎已经完成了:它将代码编译为可以实现您希望的功能。
编译器可以自由地以任何可行的方式表示成员函数的指针,而对于非虚拟函数,则可以只是函数的“正常”指针。但是尝试使用虚函数;我敢打赌后果会更加严峻。
答案 2 :(得分:1)
由于您显然需要在一些“未类型化的”对象(void*
)上按名称调用函数,同时传递多个因函数而异的参数,因此您需要某种多次调度。可能的解决方案是:
#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;
}
}
在这里,所有具有要以这种方式调用的函数的类都需要从BaseSubject
(它是CRTP)派生。这些类(此处为Cat
和Dog
,我们称它们为“主题”)具有不同的函数,具有不同的参数(bark
和meow
-当然,每个函数有多个函数主题)。每个主题都有自己的{@ {1}}字符串转换功能。这些函数不是函数指针,而是map
实例。每个对象都应调用对象的相应成员函数,并传入所需的参数。参数需要来自某种通用数据表示形式-在这里,我选择了一个简单的std::function<void (SubjectType&,const std::string&)>
。根据数据的来源,它可能是JSON或XML对象。 std::string
实例需要反序列化数据并将其作为参数传递。 std::function
被创建为每个主题类中的map
变量,其中static
实例中填充了lambda。 std::function
类查找BaseSubject
实例并调用它。由于主题类应始终直接从function
派生,因此类型BaseSubject<Subject>
的指针可以直接安全地强制转换为BaseSubject<Subject>*
。
请注意,根本没有不安全的类型转换-所有这些都由虚函数处理。因此,这应该是完全可移植的。每个学科课都有一个Subject*
,这是打字密集型的,但可以让您在不同的课中使用同名的函数。由于无论如何都需要对每个函数分别进行某种数据拆包,因此我们在map
中有单独的拆包lambda。
如果函数的参数仅是抽象数据结构,即map
,则可以省略lambda并执行以下操作:
const std::string&
这是通过const FunctionMap<Cat> Cat::functionMap = {
{ "meow", &Cat::meow }
};
的魔术(通过第一个参数传递std::function
)来实现的,与函数指针相反,魔术是定义明确的并且允许的。如果所有功能都具有相同的签名,这将特别有用。实际上,我们甚至可以省去this
并插入Jarod42的建议。
PS:只是为了好玩,这是一个将成员函数指针转换为函数指针失败的示例:
std::function
在我的机器上,此打印:
#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);
}
B::foo() x=B
B::foo() x=A
类不应显示“ x = A”!发生这种情况是因为成员函数指针带有一个额外的偏移量,该偏移量会在调用之前添加到B
中,以防多重继承起作用。投放会失去此补偿。因此,在调用强制转换的函数指针时,this
自动引用第一个基础对象,而this
是第二个基础对象,打印错误的值。
PPS:更加有趣: 如果我们插入Jarod42的建议:
B
程序正确打印:
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);
}
如果我们看一下B::foo() x=B
的反汇编,就会看到:
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()>
添加到1
指针的代码,从而调用this
并指向B::foo
的{{1}}基类this
。为了在B
函数中实现此目标(相反,该函数隐藏在X
中),我引入了AsFunc
模板参数,该参数使main
参数为派生类型{{ 1}},这样Obj
就必须进行添加。
答案 3 :(得分:1)
A)我可以摆脱编译器警告吗?
是-将成员函数包装在静态函数的调用中
(这是@ Jarod42基于模板的答案的低技术变体)
B)此代码在什么程度上可以保证工作?
不是(总结@Pete Becker的回答)。直到您摆脱警告为止。
这是我们所追求的目标。我们保持简单以最小化对代码的破坏。我们避免使用高级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;
}