以下段落摘自Stroustup的书“The C ++ Programming Language”(第三版)第420页:
因为指向虚拟成员的指针(在本例中为s)是一种 offset,它不依赖于对象在内存中的位置。一个 因此可以安全地在两者之间传递指向虚拟成员的指针 只要使用相同的对象布局,就可以使用不同的地址空间 都。像指向普通函数的指针一样,指向非虚拟函数 成员函数不能在地址空间之间交换。
我对本段最后一句话提出质疑。下面,您将找到一个代码段,其中指向非虚拟成员函数foo()
和foo1()
的指针在一个基础对象a
和派生对象之间交换b
,没有问题。
无法完成的是派生类中基类foo()
或foo1()
中的任何函数的重载,因为在这种情况下编译器将发出错误,如下所示
#include <iostream>
class A
{
int i;
public:
A() : i(1) {}
void foo() { std::cout << i << '\n'; }
void foo1() { std::cout << 2 * i << '\n'; }
};
class B: public A
{
int j;
public:
B() : A(), j(2) {}
// void foo() { std::cout << j << '\n'; }
};
int main()
{
typedef void (A::* PMF)();
PMF p = &B::foo; // error C2374: 'p' redefinition, multiple initialization
// if foo() is overloaded in B.
PMF q = &B::foo1;
B b;
(b.*p)();
(b.*q)();
A a;
(a.*p)();
(a.*q)();
}
答案 0 :(得分:1)
这句话是正确的:在(标准)C ++中,程序或者更确切地说是一个地址空间。正如ulidtko指出的那样,这句话指的是在不同进程的地址空间之间交换指向虚拟成员函数和非虚拟成员函数的指针的可能性。
一个类的非虚成员函数几乎是一个标准函数,它为你调用它的对象提供了一个隐式参数( this 指针)。因此,在加载时会在进程的地址空间中为其分配一些地址。确切地说,它最终会在您的地址空间中取决于您的平台以及该成员函数是否是动态链接库的一部分。关键是,对于两个进程,它不一定是相同的地址。因此,将指针传递给另一个进程并在另一个进程中执行此类函数可能会“使您的机器着火(TM)”。
虚拟成员函数仍然与非虚拟成员函数几乎相同,就像'内存中的某些地址,您在执行时跳转并将其传递给您的指针',但它通过虚函数表(vtable)而不是直接调用。因此,指向虚拟成员函数的指针几乎只是对象的虚函数表的索引。调用该函数然后执行“获取对象指针”的行,可能会增加指针以获取对象的vtable并跳转到该表的给定索引处的地址,将对象的地址作为这个指针'。所以这个通过vtable的间接使得在地址空间之间交换指向虚拟成员函数的指针起作用。
免责声明:我有点偏袒我的“我真的知道我在说什么” - 这里的舒适区。因此,如果我过度简化某些事情或更糟糕的事情,但是,从事分发虚假信息,请随意分开我的答案;)。
答案 1 :(得分:0)
指针将始终作为虚拟内存存在,因此它是正确的,您可以检查地址并看到没有指向内存地址的物理指针
因为指向虚拟成员(在此示例中为s)的指针是一种偏移量,因此它不依赖于对象在内存中的位置。因此,只要在两者中使用相同的对象布局,就可以安全地在不同的地址空间之间传递指向虚拟成员的指针。与普通函数的指针一样,指向非虚拟成员函数的指针不能在地址空间之间交换。