我可以将方法作为指针传递给接受基类方法指针的某个函数吗?像这里一样,函数tryit
接受类Object
的两个参数。应该有多态性,但是编译器抛出错误。
#include <iostream>
using namespace std;
class Object {
};
class Derived : public Object
{
private:
public:
void printit() {
cout << "Ok" << endl;
}
};
void tryit(Object* obj, void (Object::*fn)() ) {
(obj->*fn)();
}
int main() {
Derived d;
tryit(&d, &Derived::printit);
}
编译器说:
main.cc: In function ‘int main()’:
main.cc:31:15: error: cannot convert ‘void (Derived::*)()’ to ‘void (Object::*)()’
31 | tryit(&d, &Derived::printit);
| ^~~~~~~~~~~~~~~~~
| |
| void (Derived::*)()
main.cc:24:25: note: initializing argument 2 of ‘void tryit(Object*, void (Object::*)())’
24 | void tryit(Object* obj, void (Object::*fn)() ) {
| ^~~~~~~~~~~~~~~~~~~~
我不想在Object
类中使用虚方法,因为我想能够调用不同名称的函数。
如此:
typedef void (Object::*memfn)();
tryit(&d, (memfn) &Derived::printit);
但是为什么这不是隐式转换,为什么我需要手动强制转换?
不幸的是,多态性不是这样工作的。派生类的成员指针不能隐式地转换为父类的成员指针。只有指向派生类对象的指针(和引用)可以隐式转换为指向父类对象的指针。
你可以强制转换你的指针,让编译器高兴:
int main() {
Derived d;
tryit(&d, static_cast<void (Object::*)()>(&Derived::printit));
}
感谢@StoryTeller-UnslanderMonica的挖掘,标准中似乎有一个明确的祝福:
https://timsong-cpp.github.io/cppwp/n4868/expr.static.cast # 12
使用virtual
是处理多态类型的合法和安全的方法。你声称你不想使用virtual
,因为你">希望能够调用具有不同名称的函数"没有意义。
但是,如果你真的不想使用virtual
,那么考虑将tryit()
作为模板函数,例如:
template<typename T>
void tryit(T* obj, void (T::*fn)() ) {
(obj->*fn)();
}
int main() {
Derived d;
tryit(&d, &Derived::printit);
}
另外:
template<typename Callable>
void tryit(Callable fn) {
fn();
}
int main() {
Derived d;
tryit([&](){ d.printit(); });
}
或者,您可以使用没有模板的std::function
,例如:
void tryit(std::function<void()> fn) {
fn();
}
int main() {
Derived d;
tryit([&](){ d.printit(); });
}
但是为什么这不是隐式转换,为什么我需要手动强制转换?
因为在这种转换中,你有告诉编译器你拥有额外的知识来保证它是安全的。以对象指针为例:
struct A { int x; };
struct B : A { char c; };
A *pa = new B();
auto pb = static_cast<B*>(pa);
将B*
转换为A*
是隐式的。它是一个明确的基类。编译器知道在B
中有一个A
对象,并且可以继续执行它。但反过来是不成立的,你必须强制转换它(使用你的额外知识),让它知道A*
确实指向B*
。
指向成员的指针在某种程度上是相同的。
int B::* pmb = &A::x;
auto pma = static_cast<char A::*>(&B::c);
pa->*pma = 'c';
从指向A
成员的指针获取指向B
成员的指针是隐式转换。关于包含A
的B
(因此成员x
)的相同知识可供编译器使用。但它不能随意假设相反的情况。如果对象指针pa
不是真正指向B
怎么办?访问"成员那将是灾难性的。
与前面的推理相同,您需要强制转换以让编译器知道您对实际派生对象类型有额外的了解。