【问题标题】:Why do virtual functions need to be passed with a pointer and not by value(of the object)?为什么虚函数需要通过指针而不是值(对象)传递?
【发布时间】:2011-04-28 15:28:16
【问题描述】:

我想我理解虚方法和 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

【问题讨论】:

  • 什么是poly,它与什么有什么关系?您实际的虚拟通话在哪里?为什么你的setMaterial 方法可以同时接受指针和值?是不是超载了?
  • @AndreyT 这不是真正的代码,我只是写了两个可能的示例。
  • 很好,但您的问题是关于虚拟通话的。然而,您的示例(无论是真实的还是人造的)根本不包括任何相关的虚拟通话。

标签: c++ inheritance polymorphism virtual-functions object-slicing


【解决方案1】:

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

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

  • 基本类型的指针,或
  • 基本类型的引用

因此,如果您想要运行时多态性,您可以使用基本类型的 pointerreference,以下是一些如何实现运行时多态性的示例:

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 '&'

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

【讨论】:

  • +1:这个问题对我来说不是 100% 清楚。但这也是我的结论。
  • 谢谢,这可能就是我的代码中发生的事情。维基文章的第二句话是启示:“因为超类没有地方存储它们[孩子的属性]”。
  • 我不明白你的说法。写“成就”是什么意思?由于子类型多态性,setMaterial 将适用于所有Material 层次结构。多态性是setMaterial 的属性,不如说是“雇佣”。如果我们说这里的多态性是一种处理具有Material接口的各种类型的能力,那么每个getMaterial调用都会使用多态性(因为Material类本身实现了他的接口)
  • @Riga:“实现”是指使用Material* 类型的指针访问Texture 实现。
  • @Namaz,不,您写了“实现运行时多态性”,您最后的陈述“未实现”是不正确的,因为多态性是通过采用每次都实现 Material 接口的对象来实现的。正如您之前所说,函数获取的不是对象本身,而是指针或引用。它成功地与这个指针一起工作,多态正在使用中。
【解决方案2】:

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

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

【讨论】:

  • 尽管有= 符号,但Type foo = bar; 中没有分配
  • @FredOverflow:你是对的,解决了这个问题。如果Material 没有复制构造函数,我想会有。
  • 小修正:Material m = Texture() 会调用构造函数Material::Material(Texture const &)默认构造函数Material::Material() 然后赋值运算符Material::operator=(Texture const &)
  • @Mihran Hovsepyan:不。这里从来没有assignemnt。试试看。
  • @Mihran:实际上并没有。尝试使用公共默认 ctor 和 operator= 定义 Material 但私有副本 ctor。
【解决方案3】:

虚拟函数在您的两个示例中都运行良好。它们完全按照应有的方式工作。

虚函数的整体思想是,对此类函数的调用是根据调用中使用的对象的动态类型进行调度的。 (不幸的是,您没有在示例中展示如何进行这些调用。)

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

在第二种情况下,您创建Material 类型的对象。对象的动态类型为Material,所以虚调用转到Material的方法。

仅此而已。一切都按预期进行。如果您的期望与此不同,那么您应该让它们与语言更好地保持一致。

【讨论】:

  • 我认为第二个示例中的问题是纹理将在复制构造时被切片为材质。因此,在 m 上调用的任何虚函数都将不起作用(正如初学者所期望的那样)。
  • @Martin:不。问题是第二个示例中创建的对象的类型为Material。时期。在这种情况下,“切片”完全无关紧要。 “切片”发生在新对象的初始化期间。初始化无关紧要。 OP 创建了一个Material 类型的对象——这是唯一重要的事情。 OP 如何尝试初始化该对象(使用“切片”或不使用)完全无关紧要。
  • 完全不同意。他们创建了一个 Texture 类型的对象。当他们调用任何虚拟方法时,它不再是纹理而是材质对象(这是问题的症结所在)。他们得到了材料,因为纹理被切片了。所以代码按我的预期工作。问题是它没有像 OP 预期的那样工作,因为他们的对象被切片了。
  • @Martin:无关紧要。第二个代码创建了一个Material 类型的命名对象m 和一个Texture 类型的临时对象。虚拟调用通过Material 类型的对象m 执行。 Texture 类型的临时变量不以任何方式参与虚拟调用(它根本不能),这就是它完全不相关的原因。再次通过m(即Material)拨打电话。这是唯一重要的事情。这个m 是用Texture() 初始化的这一事实根本不重要。对象的 type 很重要。它是如何初始化的无关紧要。
  • @Martin:我不同意。这实际上是 OP 首先遇到这种混乱的原因:OP 假设临时 Texture 对象会以某种方式影响最终 Material 对象的动态行为。实际上并没有。在Material 对象的初始化过程中确实发生了切片。然而,由于这个问题的重点是虚拟行为,所以最重要的是坚持真正重要的事情:虚拟行为取决于对象的类型,而不取决于其他任何东西。其他一切都无关紧要。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    一旦将纹理对象分配给材质,它就会被切片到材质。因此,对 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
    

    【讨论】:

      【解决方案6】:
      Material m = Texture();
      

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

      【讨论】:

      • 它不构造然后复制;它调用复制构造函数。
      【解决方案7】:
      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& 而不是 Texture 的成员函数。

      【讨论】:

        猜你喜欢
        • 2011-10-29
        • 1970-01-01
        • 2019-10-31
        • 2020-11-16
        • 1970-01-01
        • 2010-09-20
        • 2014-04-12
        • 1970-01-01
        • 2013-06-05
        相关资源
        最近更新 更多