在 C + + 中,一个对象在编译时总是有一个固定的类型和大小,并且(如果它可以并且确实有地址的话)在其生存期内总是存在于一个固定的地址中。这些特性继承自 C 语言,有助于使两种语言都适合低级系统编程。(不过,所有这些都遵循“似乎”规则: 只要能够证明符合标准的编译器对符合标准的程序的任何行为没有可检测的影响,符合标准的编译器就可以自由地对代码做任何它喜欢的事情。)
C + + 中的 virtual函数被定义为基于对象的运行时类型来执行的(或多或少,不需要极端的语言律师) ; 当直接调用一个对象时,这将始终是对象的编译时类型,所以当 virtual函数以这种方式被调用时没有多态性。
请注意,这并不一定是这种情况: 具有 virtual函数的对象类型通常在 C + + 中实现,每个对象都有一个指向 virtual函数表的指针,这对于每种类型都是唯一的。如果需要的话,一个 C + + 的编译器可以对对象(比如 Base b; b = Derived())实现赋值,复制对象的内容和 virtual表指针,如果 Base和 Derived都是相同的大小,那么就很容易工作了。如果两个程序的大小不一样,编译器甚至可以插入代码,让程序暂停一段任意时间,以便重新排列程序中的内存,并更新所有可能的内存引用,这种方式可以被证明对程序的语义没有可检测的影响,如果没有发现这种重新排列,则终止程序: 这将是非常低效的,但是,不能保证永远停止,显然不希望赋值操作符具有这样的特性。
class Base { };
class Derived : public Base { };
Derived x; /* Derived type object created */
Base y = x; /* Copy is made (using Base's copy constructor), so y really is of type Base. Copy can cause "slicing" btw. */
由于 y 是 Base 类的实际对象,而不是原来的对象,因此调用 this 的函数是 Base 的函数。
class A { int x; };
A fn(A a)
{
return a;
}
class B : public A {
uint64_t a, b, c;
B(int x_, uint64_t a_, uint64_t b_, uint64_t c_)
: A(x_), a(a_), b(b_), c(c_) {}
};
B b1 { 10, 1, 2, 3 };
B b2 = fn(b1);
// b2.x == 10, but a, b and c?
在编写 fn的时候,还没有关于 B的知识。然而,B来源于 A,所以多态性应该允许我们使用 B调用 fn。但是,它返回的 对象应该是一个包含单个 int 的 A。
如果我们将 B的一个实例传递给这个函数,我们得到的应该只是一个没有 a,b,c 的 { int x; }。