将派生 class 中指向成员函数的指针转换为指向抽象成员函数的指针
cast a pointer to member function in derived class to a pointer to abstract member function
我正在尝试做一些似乎应该很常见的事情,但我一直找不到任何人讨论它。 this post on Whosebug 与我正在尝试做的类似,但不完全相同。
我有一个抽象基础class:
#ifndef _ABASECLASS_H_
#define _ABASECLASS_H_
using namespace std;
#include <iostream>
#define CALL_MBR_FUNC(object, ptr_to_mem_func) ((object).*(ptr_to_mem_func))
class aBaseClass
{
public:
typedef void (aBaseClass::*aBaseClass_mem_func)();
int A;
int B;
aBaseClass();
aBaseClass(int a, int b);
virtual void function1(aBaseClass_mem_func infunc) = 0;
virtual void function2() = 0;
};
#endif /* _ACLASS_H_ */
我有一个派生的 class:
#ifndef _ASUBCLASS_H_
#define _ASUBCLASS_H_
using namespace std;
#include <iostream>
#include "aBaseClass.h"
/* A simple class containing two ints and some functions to demonstrate passing via various methods. It is a subclass of aClass*/
class aSubClass: public aBaseClass
{
public:
aSubClass();
aSubClass(int a, int b);
void function1(aBaseClass_mem_func infunc);
void function2(void);
};
#endif /* _ASUBCLASS_H_ */
其中 function1 和 function2 是:
void aSubClass::function1(aBaseClass_mem_func infunc)
{
CALL_MBR_FUNC(*this, infunc)();
}
void aSubClass::function2(void)
{
A = 42;
B = 66;
}
最后,在 main()
中,我尝试针对类型 aSubClass
的对象调用 function1
,将指针传递给 aSubClass
中的 function2
:
int main (int argc, const char * argv[])
{
aSubClass eh(2,5);
// This doesn't work
aBaseClass_mem_func trythis = &aSubClass::function2;
// This also doesn't work
eh.function1(&aSubClass::function2);
return(0);
}
好的,我们可以自动将派生指针类型转换为基指针类型。我现在读到我们不能将派生成员函数指针传递给基成员函数指针。我想我明白为什么(派生成员函数可能会使用派生 class 中存在但基 class 中不存在的东西)。
但我正在尝试构建一个包含两类 classes 的库(源自两个基础 classes)。称它们为 baseclass1 和 baseclass2。来自 baseclass1 的任何派生 class 中的成员函数之一需要能够从来自 baseclass2 的任何派生 class 中传递特定成员函数。我可以使用一些技巧来执行必要的演员表吗?我是否必须使用 explicit
关键字并以某种方式定义转换?
你可以大大缩短这个例子:
struct B {
virtual void foo() = 0;
};
struct D : B {
void foo() override { }
};
int main() {
void (B::*ptr)() = &D::foo; // error: cannot initialize a variable of
// type 'void (B::*)()' with an rvalue of type
// 'void (D::*)()': different classes ('B' vs 'D')
}
错误消息,至少在 clang 上,是非常清楚的。 gcc 只是说无法初始化。问题只是您不能隐式 将指向派生成员的指针转换为指向基成员的指针。但是您可以 明确地 和 static_cast
:
void (B::*ptr)() =
static_cast<void (B::*)()>(&D::foo); // ok!
旁注:请从您的代码中删除 CALL_MBR_FUNC
宏,并且永远不要再写这样的东西。
为什么不起作用:
您可以这样理解成员函数:
struct Foo {
void go () { }
} ;
也可以表示为:
void go ( Foo* this ) { }
所以,这个:
typedef void(Foo::*MemberFunctionPtr)() ;
有点像这样:
typedef void(*MemberFunctionPtrForFoo)(Foo*) ;
但是,如果您有这样的子class:
struct Bar : public Foo {
void go2 () { }
} ;
那个函数也有点像这样:
void go2 ( Bar* this ) { }
所以当你获取 Bar::go2
的地址时,你基本上得到了一个指向看起来像 void go2 ( Bar* this )
的函数的指针。为什么这是个问题?
好吧,让我们看看这意味着什么...
如果你有这个功能:
void function ( Foo * this ) ;
你要这样做:
Bar * bar = new Bar () ;
function ( bar ) ;
这会起作用(应该如此)。 C++ 好心地让你能够做这样的事情:
void(*functionPtr)(Bar*) = &Foo::go ;
但是,假设您拥有此功能:
void function ( Bar * this ) ;
而你这样做了:
Foo * foo = new Foo() ;
function ( foo ) ;
这行不通,因为 foo
不是 [必然] Bar
。你可以 static_cast
那是你告诉编译器 "no, really, I'm pretty sure I know what I'm doing" 的方式(与 reinterpret_cast
相反,这是你告诉编译器 "you're stupid; I know what I'm doing." 的方式)
因此,它也不会让您强制转换成员函数。
另一个答案说 static_cast
可以转换成员函数,但这只是因为 static_cast
被允许进行隐式转换的反向操作(cv 限定除外)。你可以做到,但它有同样的警告。
免责声明:这是一个相当简化的规范版本,但它明白了要点。
大多数情况下更好的解决方案:
关于 [可能] 更好的解决方案 [除非绝对性能是关键]:boost::function
(或者,从 C++11 开始 std::function
)。这是一个 "functor".
您的成员函数可以改写为:
class Foo {
void function ( boost::function<void()> function ) { }
} ;
仿函数对象可以用任何可以用指定原型调用的东西构造(在这种情况下,不带参数并返回 void
)。例如,您可以传递 C 函数的地址。
您可以做的另一件事是 "bind" 函数(基本上是获取参数并创建函数)。为此有 boost::bind
。
例如,您可以这样做:
Foo foo ;
Bar bar ;
foo.function ( boost::bind(&Bar::go2,&bar) ) ;
boost 绑定将某个函数作为第一个参数。如果该函数是成员函数,下一个参数必须是可以调用指定方法的 class 的实例(在这种情况下它被复制)或指向指定的 class 的指针可以调用方法(在这种情况下它被引用)。该示例实际上会导致 foo
实例调用 bar
实例(而不是自身),但您可以传递 &foo
。
您还可以更有创意:
class Foo {
void function ( boost::function<void(int)> function ) {
function ( 1 ) ;
}
void go2 ( int a , int b ) {
cout << a << " " << b << endl ;
}
} ;
Foo foo ;
foo.function ( boost::bind(&Foo::go2,&foo,_1,2) ) ;
绑定抓取:
Foo::go2
的成员函数指针
- 对
foo
实例的引用(或 'pointer')
- "the first argument of the resultant function," 的占位符,它将成为
go2
调用中的第一个参数
- 数字 2,它将成为调用
go2
时的第二个参数
这是将打印到控制台的内容:
1 2
这是一个非常强大的工具,它将带您进入函数式编程的奇妙世界,同时让您的生活更轻松。 (这也会让像@CortAmmon 这样的人讨厌你。)
我正在尝试做一些似乎应该很常见的事情,但我一直找不到任何人讨论它。 this post on Whosebug 与我正在尝试做的类似,但不完全相同。
我有一个抽象基础class:
#ifndef _ABASECLASS_H_
#define _ABASECLASS_H_
using namespace std;
#include <iostream>
#define CALL_MBR_FUNC(object, ptr_to_mem_func) ((object).*(ptr_to_mem_func))
class aBaseClass
{
public:
typedef void (aBaseClass::*aBaseClass_mem_func)();
int A;
int B;
aBaseClass();
aBaseClass(int a, int b);
virtual void function1(aBaseClass_mem_func infunc) = 0;
virtual void function2() = 0;
};
#endif /* _ACLASS_H_ */
我有一个派生的 class:
#ifndef _ASUBCLASS_H_
#define _ASUBCLASS_H_
using namespace std;
#include <iostream>
#include "aBaseClass.h"
/* A simple class containing two ints and some functions to demonstrate passing via various methods. It is a subclass of aClass*/
class aSubClass: public aBaseClass
{
public:
aSubClass();
aSubClass(int a, int b);
void function1(aBaseClass_mem_func infunc);
void function2(void);
};
#endif /* _ASUBCLASS_H_ */
其中 function1 和 function2 是:
void aSubClass::function1(aBaseClass_mem_func infunc)
{
CALL_MBR_FUNC(*this, infunc)();
}
void aSubClass::function2(void)
{
A = 42;
B = 66;
}
最后,在 main()
中,我尝试针对类型 aSubClass
的对象调用 function1
,将指针传递给 aSubClass
中的 function2
:
int main (int argc, const char * argv[])
{
aSubClass eh(2,5);
// This doesn't work
aBaseClass_mem_func trythis = &aSubClass::function2;
// This also doesn't work
eh.function1(&aSubClass::function2);
return(0);
}
好的,我们可以自动将派生指针类型转换为基指针类型。我现在读到我们不能将派生成员函数指针传递给基成员函数指针。我想我明白为什么(派生成员函数可能会使用派生 class 中存在但基 class 中不存在的东西)。
但我正在尝试构建一个包含两类 classes 的库(源自两个基础 classes)。称它们为 baseclass1 和 baseclass2。来自 baseclass1 的任何派生 class 中的成员函数之一需要能够从来自 baseclass2 的任何派生 class 中传递特定成员函数。我可以使用一些技巧来执行必要的演员表吗?我是否必须使用 explicit
关键字并以某种方式定义转换?
你可以大大缩短这个例子:
struct B {
virtual void foo() = 0;
};
struct D : B {
void foo() override { }
};
int main() {
void (B::*ptr)() = &D::foo; // error: cannot initialize a variable of
// type 'void (B::*)()' with an rvalue of type
// 'void (D::*)()': different classes ('B' vs 'D')
}
错误消息,至少在 clang 上,是非常清楚的。 gcc 只是说无法初始化。问题只是您不能隐式 将指向派生成员的指针转换为指向基成员的指针。但是您可以 明确地 和 static_cast
:
void (B::*ptr)() =
static_cast<void (B::*)()>(&D::foo); // ok!
旁注:请从您的代码中删除 CALL_MBR_FUNC
宏,并且永远不要再写这样的东西。
为什么不起作用:
您可以这样理解成员函数:
struct Foo {
void go () { }
} ;
也可以表示为:
void go ( Foo* this ) { }
所以,这个:
typedef void(Foo::*MemberFunctionPtr)() ;
有点像这样:
typedef void(*MemberFunctionPtrForFoo)(Foo*) ;
但是,如果您有这样的子class:
struct Bar : public Foo {
void go2 () { }
} ;
那个函数也有点像这样:
void go2 ( Bar* this ) { }
所以当你获取 Bar::go2
的地址时,你基本上得到了一个指向看起来像 void go2 ( Bar* this )
的函数的指针。为什么这是个问题?
好吧,让我们看看这意味着什么...
如果你有这个功能:
void function ( Foo * this ) ;
你要这样做:
Bar * bar = new Bar () ;
function ( bar ) ;
这会起作用(应该如此)。 C++ 好心地让你能够做这样的事情:
void(*functionPtr)(Bar*) = &Foo::go ;
但是,假设您拥有此功能:
void function ( Bar * this ) ;
而你这样做了:
Foo * foo = new Foo() ;
function ( foo ) ;
这行不通,因为 foo
不是 [必然] Bar
。你可以 static_cast
那是你告诉编译器 "no, really, I'm pretty sure I know what I'm doing" 的方式(与 reinterpret_cast
相反,这是你告诉编译器 "you're stupid; I know what I'm doing." 的方式)
因此,它也不会让您强制转换成员函数。
另一个答案说 static_cast
可以转换成员函数,但这只是因为 static_cast
被允许进行隐式转换的反向操作(cv 限定除外)。你可以做到,但它有同样的警告。
免责声明:这是一个相当简化的规范版本,但它明白了要点。
大多数情况下更好的解决方案:
关于 [可能] 更好的解决方案 [除非绝对性能是关键]:boost::function
(或者,从 C++11 开始 std::function
)。这是一个 "functor".
您的成员函数可以改写为:
class Foo {
void function ( boost::function<void()> function ) { }
} ;
仿函数对象可以用任何可以用指定原型调用的东西构造(在这种情况下,不带参数并返回 void
)。例如,您可以传递 C 函数的地址。
您可以做的另一件事是 "bind" 函数(基本上是获取参数并创建函数)。为此有 boost::bind
。
例如,您可以这样做:
Foo foo ;
Bar bar ;
foo.function ( boost::bind(&Bar::go2,&bar) ) ;
boost 绑定将某个函数作为第一个参数。如果该函数是成员函数,下一个参数必须是可以调用指定方法的 class 的实例(在这种情况下它被复制)或指向指定的 class 的指针可以调用方法(在这种情况下它被引用)。该示例实际上会导致 foo
实例调用 bar
实例(而不是自身),但您可以传递 &foo
。
您还可以更有创意:
class Foo {
void function ( boost::function<void(int)> function ) {
function ( 1 ) ;
}
void go2 ( int a , int b ) {
cout << a << " " << b << endl ;
}
} ;
Foo foo ;
foo.function ( boost::bind(&Foo::go2,&foo,_1,2) ) ;
绑定抓取:
Foo::go2
的成员函数指针
- 对
foo
实例的引用(或 'pointer')
- "the first argument of the resultant function," 的占位符,它将成为
go2
调用中的第一个参数
- 数字 2,它将成为调用
go2
时的第二个参数
这是将打印到控制台的内容:
1 2
这是一个非常强大的工具,它将带您进入函数式编程的奇妙世界,同时让您的生活更轻松。 (这也会让像@CortAmmon 这样的人讨厌你。)