什么是对象切片?

What is object slicing?

提问人:Frankomania 提问时间:11/8/2008 最后编辑:2240Frankomania 更新时间:4/5/2022 访问量:257392

问:

在 c++ 中,什么是对象切片,它何时发生?

C 继承 C++-FAQ 对象切片

评论


答:

36赞 The Archetypal Paul 11/8/2008 #1

谷歌中“C++切片”的第三个匹配给了我这篇维基百科文章 http://en.wikipedia.org/wiki/Object_slicing 和这个(激烈的,但前几篇文章定义了问题):http://bytes.com/forum/thread163565.html

因此,当您将子类的对象分配给超类时。超类对子类中的附加信息一无所知,也没有空间存储它,因此附加信息被“切掉”。

如果这些链接没有提供足够的信息来“好答案”,请编辑您的问题,让我们知道您还在寻找什么。

730赞 David Dibben 11/8/2008 #2

“切片”是指将派生类的对象分配给基类的实例,从而丢失部分信息 - 其中一些信息被“切片”掉。

例如

class A {
   int foo;
};

class B : public A {
   int bar;
};

因此,该类型的对象具有两个数据成员,以及 .Bfoobar

然后,如果你要写这个:

B b;

A a = b;

然后,关于成员的信息在 中丢失。bbara

评论

72赞 Blair Conrad 11/8/2008
信息量很大,但请参阅 stackoverflow.com/questions/274626#274636,了解方法调用期间如何发生切片的示例(这比普通赋值示例更能强调危险)。
67赞 Karl Bielefeldt 2/2/2011
有趣。我已经用 C++ 编程 15 年了,我从来没有想到过这个问题,因为我总是通过引用传递对象作为效率和个人风格的问题。去展示好习惯如何帮助你。
13赞 8/12/2011
@Felix谢谢,但我不认为回溯(因为不是指针算术)会起作用,现在是具有副本的类型的对象。我认为现在把它扔回去是错误的。A a = b;aAB::foo
52赞 fgp 1/22/2013
这不是“切片”,或者至少是它的良性变体。如果你这样做,就会出现真正的问题。你可能认为你已经复制到了 ,但你没有!您已经复制了 to 的一部分(继承自 的部分),而 的其他部分保持不变。 现在是一个弗兰肯斯坦式的生物,由几个位和一些块组成。呸!投反对票,因为我认为答案非常误导。B b1; B b2; A& b2_ref = b2; b2 = b1b1b2b1b2b1BAb2b2b1b2
33赞 curiousguy 6/29/2013
@fgp 您的评论应为“如果您出现真正的问题”......派生自具有非虚拟赋值运算符的类。甚至是为了推导吗?它没有虚拟功能。如果从类型派生,则必须处理其成员函数可以被调用的事实!B b1; B b2; A& b2_ref = b2; b2_ref = b1A
180赞 Black 11/8/2008 #3

如果您有基类和派生类,则可以执行以下操作。AB

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

现在,该方法需要 的副本。但是,该对象无法完全复制,因为该类可能会发明其他成员变量,这些变量不在其基类中。wantAnAderivedderivedBA

因此,要调用 ,编译器将“切除”派生类的所有其他成员。结果可能是您不想创建的对象,因为wantAnA

  • 它可能不完整,
  • 它的行为类似于一个对象(类的所有特殊行为都丢失了)。AB

评论

56赞 fgp 1/23/2013
C++ 不是 Java!如果(顾名思义!)想要一个 ,那么这就是它得到的。而 的实例 ,呃,表现得像一个 .这有什么令人惊讶的?wantAnAAAA
107赞 Black 3/3/2013
@fgp:这很令人惊讶,因为你没有给函数传递 A
12赞 Black 4/7/2013
@fgp:行为是相似的。但是,对于普通的 C++ 程序员来说,这可能不那么明显。据我了解,没有人在“抱怨”。这只是关于编译器如何处理这种情况。恕我直言,最好通过传递 (const) 引用来完全避免切片。
10赞 Black 5/28/2013
@ThomasW 不,我不会抛弃继承,而是使用引用。如果 wantAnA 的签名是无效的 wantAnA(const A & myA),则不存在切片。而是传递对调用方对象的只读引用。
16赞 pqnet 8/7/2014
问题主要出在编译器执行的自动转换上 到 类型 。隐式强制转换始终是 C++ 中意外行为的来源,因为通过查看本地代码通常很难理解发生了强制转换。derivedA
34赞 Walter Bright 11/8/2008 #4

切片问题很严重,因为它会导致内存损坏,并且很难保证程序不会受到影响。为了从语言中设计它,支持继承的类应该只能通过引用(而不是通过值)来访问。D 编程语言具有此属性。

考虑类 A 和派生自 A 的类 B。如果 A 部件具有指针 p,并且 B 实例将 p 指向 B 的附加数据,则可能会发生内存损坏。然后,当其他数据被切掉时,p 指向垃圾。

评论

3赞 foraidt 11/8/2008
请解释内存损坏是如何发生的。
5赞 Walter Bright 11/11/2008
我忘记了复制 ctor 会重置 vptr,我的错误。但是,如果 A 有一个指针,并且 B 将其设置为指向 B 被切掉的部分,您仍然可能会损坏。
21赞 Weeble 2/11/2009
这个问题不仅限于切片。任何包含指针的类都将具有默认赋值运算符和复制构造函数的可疑行为。
3赞 Bjarke Freund-Hansen 7/25/2009
@Weeble - 这就是为什么在这些情况下要覆盖默认的析构函数、赋值运算符和复制构造函数。
8赞 j_random_hacker 10/24/2012
@Weeble:对象切片比一般指针修复更糟糕的是,为了确定已防止切片的发生,基类必须为每个派生类提供转换构造函数。(为什么?任何遗漏的派生类都容易被基类的复制 ctor 选取,因为隐式转换为 .)这显然违背了开闭原则,维护负担很大。DerivedBase
7赞 Steve Steiner 11/9/2008 #5

所以。。。为什么丢失派生信息是不好的?...因为派生类的作者可能更改了表示形式,因此切除额外信息会更改对象所表示的值。如果派生类用于缓存对某些操作更有效的表示形式,但转换回基本表示形式的成本很高,则可能会发生这种情况。

还认为有人也应该提到你应该做些什么来避免切片...... 获取 C++ 编码标准、101 条规则指南和最佳实践的副本。处理切片是#54。

它提出了一个稍微复杂的模式来完全处理这个问题:有一个受保护的复制构造函数,一个受保护的纯虚拟DoClone,以及一个带有断言的公共克隆,该断言将告诉你(进一步的)派生类是否未能正确实现DoClone。(Clone 方法可对多态对象进行适当的深度复制。

您还可以在基本显式上标记复制构造函数,这允许在需要时进行显式切片。

评论

3赞 curiousguy 8/5/2012
"您也可以在 base explicit“ 上标记复制构造函数”,这根本济于事。
8赞 ididak 11/9/2008 #6

C++ 中的切片问题源于其对象的值语义,这主要是由于与 C 结构的兼容性。您需要使用显式引用或指针语法来实现大多数其他执行对象的语言中发现的“正常”对象行为,即对象始终通过引用传递。

简短的回答是,通过按值将派生对象分配给基对象来对对象进行切片,即剩余对象只是派生对象的一部分。为了保留值语义,切片是一种合理的行为,并且有其相对罕见的用途,这在大多数其他语言中是不存在的。有些人认为它是 C++ 的一个特性,而许多人认为它是 C++ 的怪癖/错误特性之一。

评论

6赞 curiousguy 11/27/2011
"“正常”对象行为“,这不是”正常对象行为“,这是参考语义。它与 C、兼容性或其他任何随机 OOP 牧师告诉你的无意义无关。struct
4赞 fgp 1/23/2013
@curiousguy 阿门,兄弟。看到 C++ 经常因为不是 Java 而受到抨击,这是可悲的,而值语义是使 C++ 如此疯狂强大的原因之一。
0赞 Croll 11/17/2018
这不是一个功能,不是一个怪癖/错误功能。这是正常的堆栈复制行为,因为调用具有 arg 或(相同)分配堆栈变量类型的函数必须在内存中占用确切的字节,并且可能对齐,也许这就是为什么“赋值”(on-stack-copy)不会复制派生类成员,它们的偏移量超出了 sizeof。为了避免“丢失数据”,只需像其他人一样使用指针,因为指针内存在位置和大小上是固定的,而堆栈非常不稳定Basesizeof(Base)
1赞 John Z. Li 5/2/2019
绝对是 C++ 的错误功能。应禁止将派生对象分配给基对象,而将派生对象绑定到基类的引用或指针应该是可以的。
4赞 Minok 7/25/2009 #7

在我看来,切片并不是一个大问题,除非你自己的类和程序架构/设计不佳。

如果我将子类对象作为参数传递给一个方法,该方法采用超类类型的参数,我当然应该意识到这一点并知道内部,被调用的方法将仅与超类(又名基类)对象一起使用。

在我看来,只有一种不合理的期望,即提供请求基类的子类会以某种方式导致子类特定的结果,这会导致切片成为一个问题。它要么在使用方法时设计不佳,要么子类实现不佳。我猜这通常是为了权宜之计或性能提升而牺牲了良好的 OOP 设计的结果。

评论

4赞 Arafangion 12/22/2010
但请记住,Minok,您不是在传入该对象的引用。您正在传递该对象的新副本,但在此过程中使用基类复制它。
0赞 Dude 10/18/2012
基类上的受保护副本/赋值,此问题已解决。
1赞 Dude 10/18/2012
你是对的。良好的做法是使用抽象基类或限制对复制/赋值的访问。然而,一旦它在那里就不那么容易被发现,也很容易忘记照顾。使用 sliced 调用虚拟方法 *如果你没有违反访问,这可能会使神秘的事情发生。
1赞 Minok 7/26/2014
我记得我在大学的 C++ 编程课程中,有一些长期的最佳实践,对于我们创建的每个类,我们都需要编写默认构造函数、复制构造函数和赋值运算符,以及析构函数。这样一来,你就可以在编写类时确保复制构造等按照你需要的方式进行......而不是后来出现一些奇怪的行为。
5赞 haberdar 1/29/2012 #8

1. 切片问题的定义

如果 D 是基类 B 的派生类,则可以将 Derived 类型的对象分配给 Base 类型的变量(或参数)。

class Pet
{
 public:
    string name;
};
class Dog : public Pet
{
public:
    string breed;
};

int main()
{   
    Dog dog;
    Pet pet;

    dog.name = "Tommy";
    dog.breed = "Kangal Dog";
    pet = dog;
    cout << pet.breed; //ERROR

尽管允许上述赋值,但赋值给变量 pet 将丢失其 breed 字段。这称为切片问题

2. 如何解决切片问题

为了解决这个问题,我们使用指向动态变量的指针。

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

在这种情况下,动态变量的任何数据成员或成员函数都没有 被 ptrD(后代类对象)指向将丢失。此外,如果需要使用函数,则该函数必须是虚拟函数。

评论

8赞 curiousguy 2/18/2012
我理解“切片”部分,但我不明白“问题”。不属于类(数据成员)的某些状态没有复制到变量中,这怎么会成为问题?显然,代码只对数据成员感兴趣。如果不需要切片,它绝对是一个“问题”,但我在这里没有看到。dogPetbreedpetPet
4赞 curiousguy 2/18/2012
"((Dog *)ptrP)“我建议使用static_cast<Dog*>(ptrP)
0赞 Dude 10/18/2012
我建议指出,当通过“ptrP”删除时,你会使字符串“breed”最终在没有虚拟析构函数的情况下泄漏内存(不会调用“string”的析构函数)......为什么你展示的东西有问题?解决方法主要是适当的类设计。在这种情况下,问题在于,在继承时写下构造函数来控制可见性是乏味且容易被遗忘的。你的代码不会接近危险区域,因为不涉及甚至没有提到多态性(切片会截断你的对象,但不会使你的程序崩溃,这里)。
26赞 fgp 1/23/2013
-1 这完全无法解释实际问题。C++ 具有值语义,而不是像 Java 那样的引用语义,因此这一切都完全是意料之中的。“修复”确实是真正可怕的 C++ 代码的一个例子。通过动态分配来“修复”不存在的问题,例如这种类型的切片,会导致代码错误、内存泄漏和性能不佳。请注意,在某些情况下,切片很糟糕,但这个答案没有指出它们。提示:如果您通过引用进行分配,麻烦就开始了。
1赞 Nicholas Pipitone 9/17/2020
必须给出 -1,这是编译时错误,而不是运行时错误,Pet::breed 不存在。
4赞 Dude 10/18/2012 #9

好的,在阅读了许多解释对象切片的帖子后,我会尝试一下,但不是它如何成为问题。

可能导致内存损坏的恶性方案如下:

  • 类在多态基类上提供(意外的,可能是编译器生成的)赋值。
  • 客户端复制派生类的实例并对其进行切片。
  • 客户端调用访问切片状态的虚拟成员函数。

评论

0赞 Jeremy Friesner 11/17/2022
如果客户端在(复制到)基类对象上调用虚拟方法,则调用的虚拟方法将是基类中定义的虚拟方法,而不是子类中定义的虚拟方法。或者换句话说,赋值运算符不会修改基类对象的 vtable-pointer。
0赞 quidkid 11/29/2012 #10
class A 
{ 
    int x; 
};  

class B 
{ 
    B( ) : x(1), c('a') { } 
    int x; 
    char c; 
};  

int main( ) 
{ 
    A a; 
    B b; 
    a = b;     // b.c == 'a' is "sliced" off
    return 0; 
}

评论

5赞 Alexis Pigeon 11/29/2012
你介意提供一些额外的细节吗?您的答案与已经发布的答案有何不同?
3赞 looper 11/29/2012
我想更多的解释也不错。
623赞 fgp 1/22/2013 #11

这里的大多数答案都无法解释切片的实际问题是什么。他们只解释了切片的良性情况,而不是危险的情况。假设,像其他答案一样,你正在处理两个类和 ,其中(公开)派生自 。ABBA

在这种情况下,C++ 允许您将 的实例传递给 的赋值运算符(以及复制构造函数)。这之所以有效,是因为 的实例可以转换为 ,这是赋值运算符和复制构造函数期望其参数的内容。BABconst A&

良性案例

B b;
A a = b;

那里没有发生任何不好的事情 - 你要求一个实例是 的副本,而这正是你得到的。当然,不会包含一些成员,但应该如何呢?毕竟,它不是一个,所以它甚至没有听说过这些成员,更不用说能够存储它们了。ABabAB

诡谲的案件

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

你可能会认为这将是之后的副本。但是,唉,事实并非如此!如果你检查它,你会发现这是一个弗兰肯斯坦式的生物,由一些块(继承自 )和一些块(仅包含的块)组成。哎哟!b2b1b2b1BAb2B

发生了什么事?好吧,默认情况下,C++ 不会将赋值运算符视为 .因此,该行将调用 的赋值运算符 ,而不是 的赋值运算符。这是因为,对于非虚函数,声明的(形式上是:静态的)类型(是 )决定了调用哪个函数,而不是实际的(形式上是:动态的)类型(将是 ,因为引用了 的实例)。现在,赋值运算符显然只知道 中声明的成员,因此它将只复制这些成员,而保留添加的成员不变。virtuala_ref = b1ABA&Ba_refBAAB

解决方案

只分配给对象的一部分通常没有什么意义,但不幸的是,C++没有提供内置的方法来禁止这样做。但是,您可以自己滚动。第一步是使赋值运算符虚拟化。这将保证调用的始终是实际类型的赋值运算符,而不是声明类型的赋值运算符。第二步是用于验证分配的对象是否具有兼容类型。第三步是在 (protected!) 成员中执行实际赋值,因为 可能想要使用 's 来复制 's, 成员。dynamic_castassign()Bassign()Aassign()A

class A {
public:
  virtual A& operator= (const A& a) {
    assign(a);
    return *this;
  }

protected:
  void assign(const A& a) {
    // copy members of A from a to this
  }
};

class B : public A {
public:
  virtual B& operator= (const A& a) {
    if (const B* b = dynamic_cast<const B*>(&a))
      assign(*b);
    else
      throw bad_assignment();
    return *this;
  }

protected:
  void assign(const B& b) {
    A::assign(b); // Let A's assign() copy members of A from b to this
    // copy members of B from b to this
  }
};

请注意,为了方便起见,'s 会协变覆盖返回类型,因为它知道它返回的是 的实例。Boperator=B

评论

16赞 supercat 8/13/2013
恕我直言,问题在于继承可能隐含两种不同类型的可替代性:要么可以将任何值赋予期望值的代码,要么将任何派生引用用作基本引用。我希望看到一种具有类型系统的语言,可以分别解决这两个概念。在许多情况下,派生引用应该可以替换基本引用,但派生实例不应替代基本引用;在许多情况下,实例应该是可转换的,但引用不应该替换。derivedbase
28赞 Mladen B. 11/14/2013
我不明白你的“奸诈”案子有什么不好的。您说您想要:1) 获取对类 A 对象的引用,以及 2) 将对象 b1 转换为类 A 并将其内容复制到类 A 的引用。这里实际上错误的是给定代码背后的正确逻辑。换句话说,你拍了一个小图像框(A),把它放在一个更大的图像(B)上,然后你通过那个框架画画,后来抱怨你的大图像现在看起来很丑:)但是,如果我们只考虑那个框架区域,它看起来相当不错,就像画家想要的那样,对吧?:)
15赞 fgp 11/17/2013
问题在于,换句话说,C++ 默认假定了一种非常强的可替代性——它要求基类的操作在子类实例上正常工作。甚至对于编译器自动生成的操作(如赋值)。因此,在这方面不搞砸自己的操作是不够的,您还必须显式禁用编译器生成的错误操作。或者当然,远离公共继承,这通常是一个很好的建议;-)
20赞 Siyuan Ren 8/22/2014
另一种常见的方法是简单地禁用复制和赋值运算符。对于继承层次结构中的类,通常没有理由使用 value 而不是引用或指针。
20赞 paulm 3/2/2015
什么?我不知道操作员可以被标记为虚拟
3赞 Santosh 3/13/2014 #12

切片意味着当子类的对象按值传递或返回时,或从需要基类对象的函数传递或返回时,将丢弃子类添加的数据。

解释:请考虑以下类声明:

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

由于基类复制函数对派生函数一无所知,因此只复制派生的基部分。这通常称为切片。

57赞 geh 8/23/2014 #13

这些都是很好的答案。我只想添加一个按值传递对象与引用传递对象的执行示例:

#include <iostream>

using namespace std;

// Base class
class A {
public:
    A() {}
    A(const A& a) {
        cout << "'A' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am an 'A'" << endl; }
};

// Derived class
class B: public A {
public:
    B():A() {}
    B(const B& a):A(a) {
        cout << "'B' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am a 'B'" << endl; }
};

void g(const A & a) {
    a.run();
}

void h(const A a) {
    a.run();
}

int main() {
    cout << "Call by reference" << endl;
    g(B());
    cout << endl << "Call by copy" << endl;
    h(B());
}

输出为:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'

评论

0赞 Adrian 7/10/2018
你好。很好的答案,但我有一个问题。如果我做这样的事情 ** dev d;base* b = &d;** 切片也发生了?
1赞 Vishal Sharma 12/25/2019
@Adrian 如果在派生类中引入了一些新的成员函数或成员变量,则无法直接从基类指针访问它们。但是,您仍然可以从重载的基类虚拟函数内部访问它们。请参阅: godbolt.org/z/LABx33
-1赞 Varun Kumar 5/11/2016 #14

当派生类对象分配给基类对象时,派生类对象的其他属性将从基类对象中切除(丢弃)。

class Base { 
int x;
 };

class Derived : public Base { 
 int z; 
 };

 int main() 
{
Derived d;
Base b = d; // Object Slicing,  z of d is sliced off
}
-1赞 Ghulam Moinul Quadir 7/19/2017 #15

将派生类对象分配给基类对象时,派生类对象的所有成员都将复制到基类对象中,但基类中不存在的成员除外。这些成员被编译器切除。 这称为对象切片。

下面是一个示例:

#include<bits/stdc++.h>
using namespace std;
class Base
{
    public:
        int a;
        int b;
        int c;
        Base()
        {
            a=10;
            b=20;
            c=30;
        }
};
class Derived : public Base
{
    public:
        int d;
        int e;
        Derived()
        {
            d=40;
            e=50;
        }
};
int main()
{
    Derived d;
    cout<<d.a<<"\n";
    cout<<d.b<<"\n";
    cout<<d.c<<"\n";
    cout<<d.d<<"\n";
    cout<<d.e<<"\n";


    Base b = d;
    cout<<b.a<<"\n";
    cout<<b.b<<"\n";
    cout<<b.c<<"\n";
    cout<<b.d<<"\n";
    cout<<b.e<<"\n";
    return 0;
}

它将生成:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'

评论

1赞 Stefan Fabian 3/29/2019
投了反对票,因为这不是一个好例子。如果您不将 d 复制到 b,而是使用指针,在这种情况下 d 和 e 仍然存在,但 Base 没有这些成员,它也不起作用。您的示例仅显示您无法访问该类没有的成员。
-2赞 Martin B. 10/13/2017 #16

我只是遇到了切片问题,并迅速降落在这里。因此,让我在此基础上加上两分钱。

让我们举一个来自“生产代码”(或类似代码)的例子:


假设我们有一个调度动作的东西。例如,控制中心 UI。
此 UI 需要获取当前能够调度的事物列表。因此,我们定义了一个包含调度信息的类。我们称之为 .所以 an 有一些成员变量。为简单起见,我们只有 2,分别是 a 和 a .然后它有一个 which 只是执行成员。
ActionActionstd::string namestd::function<void()> fvoid activate()f

因此,UI得到了一个。想象一下一些函数,例如:std::vector<Action>

void push_back(Action toAdd);

现在,我们已经确定了它从 UI 的角度来看是什么样子的。到目前为止没有问题。但是从事这个项目的其他一些人突然决定,在对象中有一些专门的操作需要更多信息。出于什么原因。这也可以通过 lambda 捕获来解决。此示例不是从代码中 1-1 获取的。Action

所以这家伙从中衍生出来,添加他自己的味道。
他将他自制的课程的一个实例传递给了 ,但随后程序就失控了。
Actionpush_back

那么发生了什么?
您可能已经猜到了:对象已被切片。

来自实例的额外信息已丢失,现在容易出现未定义的行为。f


我希望这个例子能为那些在谈论 s 和 s 以某种方式派生时无法真正想象事物的人带来启示。AB

13赞 Kartik Maheshwari 3/7/2018 #17

在 C++ 中,可以将派生类对象分配给基类对象,但另一种方式是不可能的。

class Base { int x, y; };

class Derived : public Base { int z, w; };

int main() 
{
    Derived d;
    Base b = d; // Object Slicing,  z and w of d are sliced off
}

当将派生类对象分配给基类对象时,会发生对象切片,派生类对象的其他属性被切除以形成基类对象。

14赞 Sorush 9/12/2020 #18

我看到所有答案都提到了在对数据成员进行切片时发生对象切片的情况。在这里,我举了一个例子,这些方法没有被覆盖:

class A{
public:
    virtual void Say(){
        std::cout<<"I am A"<<std::endl;
    }
};

class B: public A{
public:
    void Say() override{
        std::cout<<"I am B"<<std::endl;
    }
};

int main(){
   B b;
   A a1;
   A a2=b;

   b.Say(); // I am B
   a1.Say(); // I am A
   a2.Say(); // I am A   why???
}

B(对象 b)派生自 A(对象 a1 和 a2)。正如我们所期望的,B 和 A1 调用它们的成员函数。但从多态性的角度来看,我们并不期望由 b 分配的 a2 不会被覆盖。基本上,a2 只保存了 b 的 A 类部分,即 C++ 中的对象切片。

要解决此问题,应使用引用或指针

 A& a2=b;
 a2.Say(); // I am B

A* a2 = &b;
a2->Say(); // I am B

评论

0赞 MTV 7/10/2022
But from polymorphism viewpoint we don’t expect a2, which is assigned by b, to not be overridden. Basically, a2 only saves A-class part of b and that is object slicing in C++.这听起来不对,也不清楚。的静态类型是 这就是被调用的原因 - 我认为它与对象切片无关。a2AA::Say()