C++对象切片问题

68 阅读3分钟

C++对象切片

什么是对象切片?

定义:把一个派生类对象传值的方式赋值或传递给一个基类对象时,这个派生类对象中所有“派生类特有”的数据成员和行为都会被“切掉”,最终只剩下和基类完全相同的部分。这个过程就叫做对象切片

eg:

#include <iostream>
#include <string>

class Base {
public:
    // 虚函数,用于多态
    virtual void print_type() const {
        std::cout << "I am a Base object.\n";
    }
};

class Derived : public Base {
private:
    std::string derived_data_ = "Derived specific data"; // 派生类独有的数据

public:
    // 重写基类的虚函数
    void print_type() const override {
        std::cout << "I am a Derived object. My data is: " << derived_data_ << "\n";
    }
};

int main() {
    Derived derived_obj; // 创建一个完整的派生类对象

    std::cout << "Original object:\n";
    derived_obj.print_type();

    std::cout << "\n--- Slicing happens here ---\n";
    
    // 触发对象切片:将派生类对象赋值给一个基类对象
    Base base_obj = derived_obj; 
    
    std::cout << "Sliced object:\n";
    base_obj.print_type(); // 调用print_type()

    return 0;
}

/*
结果:
Original object:
I am a Derived object. My data is: Derived specific data

--- Slicing happens here ---
Sliced object:
I am a Base object.
*/

analysis:

  1. 执行 Base base_obj = derived_obj; 时,编译器会创建一个新的、类型为Basebase_obj对象
  2. base_obj只拥有Base类所定义的内存空间
  3. 编译器会将derived_obj中属于Base的部分(“基类子对象”)复制到base_obj
  4. derived_obj中独有的derived_data_成员因为在base_obj中没有存放的空间,所以被完全丢弃
  5. 更重要的是,base_obj就是一个纯粹的Base对象,它的虚函数表指针指向Base类的虚函数表。因此,当调用base_obj.print_type()时,执行的是Base版本的函数,多态性完全失效

如何避免对象切片?

唯一方法,就是在处理多态对象时,始终使用指针或引用

指针和引用不会创建新对象,它们只是现有对象的“别名”或“地址”,因此保留了对象的完整类型信息,不会发生切片

#include <iostream>
#include <string>

// Base 和 Derived 类的定义同上

int main() {
    Derived derived_obj;

    std::cout << "--- Avoiding slicing with a pointer ---\n";
    Base* ptr_to_base = &derived_obj; // 使用基类指针指向派生类对象
    ptr_to_base->print_type();        // 通过指针调用,多态正常工作

    std::cout << "\n--- Avoiding slicing with a reference ---\n";
    Base& ref_to_base = derived_obj;  // 使用基类引用绑定派生类对象
    ref_to_base.print_type();         // 通过引用调用,多态正常工作

    return 0;
}

/*--- Avoiding slicing with a pointer ---
I am a Derived object. My data is: Derived specific data

--- Avoiding slicing with a reference ---
I am a Derived object. My data is: Derived specific data*/

分析:

通过指针Base*)和引用Base&),没有创建任何新对象,

只是持有了原始derived_obj的地址或别名。

因此,调用虚函数时,C++的动态派发机制可以正确地找到并执行Derived类重写的版本,多态得以实现


Summarize When does slicing occur?:

  1. 按值赋值:Base b = d; (如上例)
  2. 按值传递给函数void my_func(Base b) { ... },然后用派生类对象调用 my_func(d);
  3. 在容器中存储对象std::vector<Base> vec; vec.push_back(d);最危险且最难发现的情况之一,因为容器会创建对象的副本,导致所有存入的派生类对象都被“切片”成基类对象。正确的做法是存储指针,最好是智能指针:std::vector<std::unique_ptr<Base>>