<p>Base* arr[10]不会触发对象切片,因为指针存储的是地址而非对象本体,切片仅发生在值传递或赋值给基类对象时;用std::vector<std::unique_ptr<Base>>替代更安全,但需确保Base有虚析构函数。</p>

基类指针数组本身不导致对象切片,切片只发生在值传递或直接赋值给基类对象时;用 std::vector<:unique_ptr>></:unique_ptr> 替代裸指针数组,是更安全、更现代的解法。
为什么 Base* arr[10] 不会触发对象切片
对象切片(slicing)的本质是“把派生类对象按值复制进基类对象”,而指针数组里存的是地址,不是对象本体。只要没写类似 Base b = derived_obj; 或 arr[i] = Derived();(这里 arr 是 Base[] 而非 Base*[]),就不会发生切片。
-
Base* arr[5] = {new Derived1(), new Derived2()};—— 每个元素只是指向堆上完整派生对象的指针,虚函数调用正常 - 若误写成
Base arr[5] = {Derived1(), Derived2()};—— 此时才发生切片,Derived1()构造后被强制转为Base副本,虚表丢失 - 常见误判来源:把“指针数组能多态调用”和“值数组不能多态”混为一谈,其实根本原因是存储方式(地址 vs 值),而非数组本身
用 std::vector<:unique_ptr>></:unique_ptr> 替代裸指针数组
裸指针数组要手动管理内存,容易泄漏或重复释放;std::unique_ptr 自动绑定生命周期,且支持多态删除(需 Base 有虚析构函数)。
- 必须确保
Base::~Base()是virtual,否则unique_ptr<base>删除时不会调用派生类析构函数 - 初始化示例:
std::vector<std::unique_ptr<Base>> vec; vec.push_back(std::make_unique<Derived1>()); vec.push_back(std::make_unique<Derived2>()); // 调用时自动多态:vec[0]->foo(); // 走 Derived1::foo
- 不能写
vec.push_back(std::unique_ptr<base>(new Derived1()))—— 这样会因类型不匹配编译失败;必须用std::make_unique<derived1>()</derived1>或显式转型std::unique_ptr<base>(new Derived1())(不推荐裸 new)
如果坚持用裸指针数组,这些细节不能漏
裸指针数组不是错,但必须明确承担资源管理责任,并规避常见陷阱。
立即学习“C++免费学习笔记(深入)”;
- 声明时用
Base* arr[10] = {};初始化为nullptr,避免野指针 - 析构前必须遍历 delete:for (auto p : arr) if (p) delete p;
- 禁止把栈对象地址存入该数组:
Derived d; arr[0] = &d;—— 函数返回后arr[0]成悬垂指针 - 若数组生命周期长于派生对象(比如全局/静态),必须确保派生对象不提前析构;否则多态调用将触发未定义行为
- 不能对指针数组做
sizeof(arr)/sizeof(arr[0])获取长度 —— 数组退化为指针后失效;应额外记录 size 或改用std::array
真正容易被忽略的点是析构顺序与虚析构的耦合:哪怕你用了 unique_ptr,只要 Base 缺少 virtual ~Base() = default;,派生对象的资源就大概率泄漏——这不是语法错误,而是静默的逻辑缺陷。
















