【问题标题】:how slicing of derived classes occurs?派生类的切片是如何发生的?
【发布时间】:2020-01-20 09:03:52
【问题描述】:

我无法理解切片是如何发生的?例如,在这段代码中:

class A {
public:
  virtual h() {
    cout << "type A" << endl;
  }
};

class B : public A {
public:
  virtual h() override {
    cout << "type B" << endl;
  }
};

void f(A a) {
  a.h();
}

void g(A& a) {
  a.h();
}

int main() {
  A a1 = B(); // a1 is A and doesn't recognize any B properties
  A *a = new B();
  f(*a);
  g(*a);
}

我注意到:

  1. 变量 a1 不知道它是 B,但变量 a 知道。我指的是这种情况正在发生,因为在变量 a1 中,对 B 的赋值是按值分配的,与变量 a 相反,我在其中创建了一个指向 B 的指针。

  2. 当我将变量 a 传递给不同的函数时,也会发生同样的事情 - 当我通过值传递时,它认为它是 A,但是当我通过引用传递时,它认为它是 B。

如果有人能给我更广泛和更深入的解释,我会很高兴。提前谢谢!

【问题讨论】:

  • 当您通过值而不是通过引用接收实例时会发生这种情况。解决方法:不要。
  • 请注意,整个“它认为它是 B”是因为你的类是 多态的。如果函数 h 没有被声明为 virtual 它也不会工作,即使没有切片(比如使用指针或引用时)。
  • 至于切片本身,如果您复制一个对象,那么切片就会发生。例如,在A a1 = B(); 中,您复制由B() 创建的(临时)对象,但只复制对象的A 部分。当您执行A *a = new B; 时,您不会复制对象本身,而是让a 指向实际的B 对象。
  • 变量a 只“知道”它是一个A* - 一个指向A 或派生自A 的指针。正是a 指向的对象知道它是B
  • What is object slicing?的可能重复

标签: c++ oop inheritance polymorphism pass-by-reference


【解决方案1】:
  1. 变量 a1 不知道它是一个 B

更正确:变量 a1 被声明为 A,所以它是 A。它不是 B,而且从来都不是 B。这与变量“知道”什么无关;这是关于变量的类型。 a1 是通过“切片”基本子对象的副本从 B 初始化的。

如果有人能给我更广泛和更深入的解释,我会很高兴。

间接对于运行时多态是必要的。 A 类型的对象始终是 A 类型,没有其他类型。这就是语言中的类型。原因之一是编译器必须知道对象需要多少内存。如果编译器为 A 保留内存,那么一个可能更大的派生实例如何能放入保留的内存中?

但是指向 A 的指针(或引用)既可以指向不同的 A 对象,也可以指向从 A 派生的类的基本子对象。完整的对象有多大并不重要, 指针可以指向对象的一部分,这不会影响指针本身的大小。

派生类的切片是如何发生的?

只要将派生对象转换为基本类型,就会发生切片。转换复制基类子对象。请注意,当您将派生值转换为对基的引用时,不会发生切片 - 除非您使用该引用来初始化基类型的对象。

【讨论】:

    【解决方案2】:

    在此声明中

    A a1 = B();
    

    使用类 A 的默认复制构造函数,因为对象 a1 的类型为 A。

    这个构造函数看起来像

    constexpr A( const A & );
    

    所以只有类B的对象的A类型的子对象被复制到对象a1。指向对象a1 的虚函数的指针表将包含指向它自己的虚函数的指针。

    在此声明中

    A *a = new B();
    

    指针a 指向B 类型的对象,该对象包含指向虚函数的指针表,而虚函数又包含指向B 类的虚函数的指针。

    所以在这次通话中

      g(*a);
    

    传递了对动态类型B的对象的引用(通过引用传递给函数时对象本身没有改变)。

    所以在函数内

    void g(A& a) {
      a.h();
    }.
    

    将访问包含指向类 B 的虚函数的指针的虚函数指针表。这里没有创建类 A 的新对象,因为原始对象是通过引用传递的。

    在本次通话中

      f(*a);
    

    函数的参数是按值传递的。那就是声明的函数的参数的副本初始化像

    void f(A a) {
      a.h();
    }
    

    所以实际上和声明中的情况是一样的

    A a1 = B();
    

    以上考虑。

    【讨论】:

      猜你喜欢
      • 2018-12-27
      • 2013-01-09
      • 2013-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-02
      相关资源
      最近更新 更多