为什么虚拟函数需要通过指针而不是通过(对象的)值来传递呢



我想我理解虚拟方法和vtable的概念,但我不明白为什么将对象作为指针(或引用(传递和按值传递之间有区别(哪种丢弃vtable或其他东西?(

为什么这样的东西会起作用:

Material* m = new Texture;
poly->setMaterial(m); 
// methods from Texture are called if I keep carrying the pointer around

而不是这个?:

Material m = Texture();
poly->setMaterial(m);
// methods from Material are called if I pass the value around

因为如果您传递值,那么将发生对象切片,并且无法实现运行时多态性。在您的代码中,Material m = Texture()这一行会导致对象切片。因此,即使通过指针(或引用(传递m,也无法实现运行时多态性。

此外,运行时多态性是通过以下方式实现的:

  • 基类型的指针,或
  • 基准类型参考

因此,如果你想要运行时多态性,你可以使用基本类型的指针引用,这里有几个例子可以实现运行时多态:

Material* m1 = new Texture();
poly->setMaterial(m1);     //achieved
Texture* t1= new Texture();
poly->setMaterial(t1);     //achieved
Texture t2;
poly->setMaterial( &t2);   //achieved : notice '&'
Material & m2 =  t2;
poly->setMaterial( &m2 );  //achieved : notice '&'
Material  m3;
poly->setMaterial( &m3 );  //NOT achieved : notice '&'

只有在最后一行中,您没有实现运行时多态性。

Material m = Texture()将调用构造函数Material::Material(Texture const &),或者,如果不可用,则调用Material复制构造函数,后者将构造Material而不是Texture

这无法为您构建Texture对象,因此该对象被切片为基类对象。

虚拟函数在这两个示例中都能很好地工作。他们完全按照他们应该做的工作。

虚拟函数的整体思想是,根据调用中使用的对象的动态类型来调度对此类函数的调用。(不幸的是,你没有在你的例子中展示你是如何打这些电话的。(

在第一个示例中,您创建了一个类型为Texture的对象。对象的动态类型是Texture,因此虚拟调用转到Texture的方法。

在第二种情况下,您将创建一个类型为Material的对象。对象的动态类型是Material,因此虚拟调用转到Material的方法。

这就是它的全部。一切都如人们所料。如果你的期望与此不同,那么你应该让它们更好地与语言保持一致。

因为Material m = Texture();会对对象进行切片——此时,您只有一个Material

将纹理对象指定给材质后,它将被切片为材质。因此,对m对象的任何调用都将只调度Material函数。

Material m正好有一个Material对象的空间。

使用简单的结构,我说明了切片的含义:

struct A { 
  int a;
};
struct B : public A {
  int b;
};
A objectA = B();
objectA.b = 1; // compile error, objectA does only have the properties of struct A
Material m = Texture();

这创建了一个临时Texture,然后通过复制TextureMaterial部分来创建Material。这被称为切片,通常不是您想要的。

class Base
{
    //Members       
};
class Derived1:public Base
{
    //Members
};
int main()
{
    Base obj1;
    Derived1 obj2;
    obj1 = obj2;   //Allowed Since Public Inheritance 
}

obj1 = obj2只有从基类继承的派生类obj2的成员被复制到obj1时,派生类的其他成员被切片。这只是因为基类obj1不知道派生类的成员。这种现象被称为Object Slicing

在您的情况下,当您调用Material m = Texture()时,它只包含Material的成员,因此对对象的任何函数调用都会调用Material&而不是CCD_ 27。

基于c++primer P604,一旦通过引用或指针进行调用,就可以在运行时解析虚拟函数。

在书上的示例代码中

Bulk_quote derived("xxx",50,5,.19);
print_total(cout, derived, 10);

print_total被声明为

double print_total(ostream &os, const Quote &item, size_t n){
/*definition*/
}

这里的项是通过引用传递的,所以它仍然调用派生类中的虚拟函数。

所以在你的情况下,我认为如果你的setMaterial函数得到一个Material引用参数,它仍然可以调用Texture虚拟函数。

相关内容

最新更新