请求详解C++派生类切片机制:结合代码分析现象成因
嘿,我来帮你把这个切片问题掰扯得明明白白——你观察到的现象完全正确,咱们一步步拆解背后的逻辑:
什么是“切片”?
简单说,当你把一个派生类对象按值赋值给基类对象(或者按值传递给基类类型的参数)时,编译器只会把派生类对象中属于基类的那部分成员复制到新的基类对象里,派生类自己特有的成员(包括虚函数表的正确指向)会被直接“切掉”丢弃。最终得到的就是一个纯纯的基类对象,和派生类再也没关系了。
结合你的代码逐个分析
咱们对着你的代码片段一个个看:
1. A a1 = B(); —— 赋值时的切片
这里你用一个临时的B对象给A类型的a1赋值,属于按值拷贝操作。编译器会调用A的默认拷贝构造函数,把B对象里属于A的部分复制到a1中。
a1本身的内存大小就是A类的大小,它的虚函数表指针指向的是A类的虚函数表,所以不管你原来的B对象是什么样,a1就是个标准的A对象,调用a1.h()自然只会输出type A。
2. A *a = new B(); —— 指针为什么没问题?
指针的本质只是一个内存地址,它不会改变指向对象的内存布局。这里a是A类型的指针,但它指向的是堆上完整的B对象——这个B对象保留了自己所有的成员,包括指向B类虚函数表的指针。
当通过指针调用虚函数h()时,编译器会根据对象实际的虚函数表找到对应的实现,也就是B的h(),所以这是多态的正常工作,不会发生切片。
3. 函数传参的差异:f(*a) vs g(*a)
void f(A a):按值传参导致切片
调用f(*a)时,会把*a(也就是那个完整的B对象)按值传递给参数a——这会创建一个新的A类型临时对象,只复制B中属于A的部分,派生类的信息被切掉了。所以这个临时对象的虚函数表是A的,调用a.h()输出type A。void g(A& a):按引用传参避免切片
引用本质是原对象的“别名”,不会创建新对象。这里的参数a直接引用了原来的B对象,完全保留了B的内存布局和虚函数表。调用a.h()时,依然会触发多态,执行B的h(),输出type B。
核心总结
- 切片只发生在按值操作时:赋值、按值传参、按值返回对象,这些操作都会创建新的基类对象,切掉派生类的额外信息。
- 指针/引用不会切片:它们只是指向或引用原对象,不会改变对象本身的结构,所以能正常支持多态。
内容的提问来源于stack exchange,提问作者E. Ginzburg

