新闻详情

新闻详情

首页 / 资讯中心 / 详情

1.2 对象切片:将派生类按值赋给基类,派生部分被截断 ——C++踩坑排雷手册:派生类对象按值赋值给基类时为什么会丢失派生成员数据

发布时间:2026/9/26 8:48:17来源:尧图网络
1.2 对象切片:将派生类按值赋给基类,派生部分被截断 ——C++踩坑排雷手册:派生类对象按值赋值给基类时为什么会丢失派生成员数据
1.2 C实战100例——对象切片将派生类按值赋给基类派生部分被截断拷贝构造触发类型擦除指针/引用是唯一回避手段受众在 C 容器、工厂函数、回调接口里用值语义传多态对象的开发者。会写继承和拷贝构造就行不需要提前懂内存布局。场景你把派生类对象按值赋给基类变量或者存进std::vectorBase之后调虚函数全是基类版本派生类成员全部丢了。这不是编译器优化是值语义的硬约束。这篇给你三条命令看清楚切片在哪一刻发生再给你三种回避手段。摘要对象切片是以值语义处理继承时最隐蔽的数据截断。原因很简单按值拷贝时目标对象的大小是sizeof(Base)。源对象是派生类sizeof(Derived) sizeof(Base)。拷贝只复制内存里和基类重叠的前缀部分派生类独有的成员和虚表指针从来没有被复制过——不是“丢了”是从未复制。你只需要记住一句话值语义装不下多态对象。对话正文先做一件事看看基类和派生类大小差多少。g-stdc11-O0test_slice.cpp-otest_slice./test_slice gdb-exbreak main-exrun-exprint sizeof(Component)-exprint sizeof(PlayerComponent)-exquit./test_slice如果sizeof(PlayerComponent) sizeof(Component)而且程序输出基类版本那就是这个坑。读者 我在一个游戏引擎的组件系统里把派生类PlayerComponent按值存进std::vectorComponent遍历调virtual void update()结果全是Component::update()不是PlayerComponent::update()。代码如下structComponent{virtualvoidupdate(){std::coutComponent\n;}};structPlayerComponent:publicComponent{inthealth100;virtualvoidupdate()override{std::coutPlayer: health\n;}};intmain(){std::vectorComponentcomponents;components.push_back(PlayerComponent());for(autoc:components)c.update();}输出Component不是Player: 100。我遍历用了引用为什么还是不行派生类的health去哪了总工 切片发生在push_back的拷贝构造阶段后面用引用也救不回来。第一步看内存大小g-stdc11-O0test_slice.cpp-otest_slice./test_slice gdb-exbreak main-exrun-exprint sizeof(Component)-exprint sizeof(PlayerComponent)-exquit./test_slice输出sizeof(Component) 8vptr 8 字节sizeof(PlayerComponent) 16vptr int padding。派生类比基类大 8 字节。第二步看拷贝现场push_back(PlayerComponent())实际做的事构造临时PlayerComponent16 字节vptr 指向派生类虚表。push_back在 vector 里分配sizeof(Component) 8 字节的槽位。调用Component的合成拷贝构造——把PlayerComponent的前 8 字节基类子对象含 vptr按位复制到槽位。后面 8 字节health从未被复制因为目标只有 8 字节。槽位里的 vptr 指向基类虚表。所以health不是丢了是从未进入 vector。后面用引用访问访问的是一个 8 字节的基类对象派生部分根本不存在。验证拷贝构造g-stdc11-O0-Stest_slice.cppcattest_slice.s|grep-B5-A5Component::Component(Component const)合成拷贝构造编译出来是memcpy或逐字节复制只复制 8 字节。读者 我明白了vector 存的是值所以切片。但我的 API 必须返回Component类型不能改成指针。有什么办法在接收端检测或防止切片总工 按值返回Component意味着切片必然发生这是值语义的铁律。三个层面可以防御防御一编译期禁用拷贝。删除基类拷贝构造和赋值操作符让切片编不过。structComponent{Component()default;Component(constComponent)delete;Componentoperator(constComponent)delete;virtualvoidupdate(){}};vectorComponent编译就报错从源头阻止。要存多态对象强制用指针容器。防御二运行时检测——这个不行。在拷贝构造里加typeid检查是无效的Component(constComponentother){if(typeid(*this)!typeid(other)){...}// 无效}拷贝构造执行时other已经是基类typeid(other)返回Component检测不到派生类型。别用这个方法。防御三正确设计。用std::unique_ptrComponent或std::shared_ptrComponent存多态对象。std::vectorstd::unique_ptrComponentcomponents;components.push_back(std::make_uniquePlayerComponent());for(autoup:components)up-update();// Player: 100验证g-stdc11-O0test_unique_ptr.cpp-otest_unique./test_unique输出Player: 100sizeof(unique_ptrComponent) 8对象在堆上完整保留。读者 我的容器是第三方库定义的std::vectorComponent改不了。除了改设计还有别的办法吗比如std::variant或者模板总工 容器类型改不了唯一可行路径是用std::variant替代继承。这不是修切片是从根上把运行时多态换成编译期联合体。usingComponentVariantstd::variantComponent,PlayerComponent;std::vectorComponentVariantcomponents;components.push_back(PlayerComponent());for(autov:components){std::visit([](autoc){c.update();},v);}std::visit为每个类型生成独立分发代码没有 vptr、没有切片、没有堆分配。每个元素占max(sizeof(Component), sizeof(PlayerComponent)) 16 字节所有数据完整保留。代价是每个新派生类都要加进 variant 的类型列表失去运行期动态扩展能力。如果既不能改容器也不能用 variant那就不存在解法。你必须直面设计决策要么让容器存指针推荐unique_ptr要么接受切片。验证g-stdc17-O0test_variant.cpp-otest_variant./test_variant输出Player: 100sizeof(ComponentVariant) 16。抄作业清单步骤执行命令预期输出/生效标志1g -stdc11 -O0 test_slice.cpp -o test_slice ./test_slice输出Component基类版本2gdb -ex break main -ex run -ex print sizeof(Component) -ex print sizeof(PlayerComponent) -ex quit ./test_slice输出8和163g -stdc11 -O0 -S test_slice.cpp cat test_slice.s | grep -A 10 Component::Component(Component const)合成拷贝构造只复制 8 字节4删除拷贝构造后g -stdc11 -O0 test_slice_deleted.cpp -o test_slice_deleted 21 | head -20编译报错提示 deleted function5unique_ptr版本g -stdc11 -O0 test_unique_ptr.cpp -o test_unique ./test_unique输出Player: 1006variant版本g -stdc17 -O0 test_variant.cpp -o test_variant ./test_variant输出Player: 100sizeof(ComponentVariant) 16常见卡点卡点1切片后dynamic_cast返回nullptr现象想把已切片的基类对象转回派生类失败。原因切片后对象里没有派生类虚表信息RTTI 随派生数据一起丢了。切片不可恢复必须在切片发生前用指针/引用。验证g-stdc11-O0-D_GLIBCXX_USE_RTTItest_dynamic_cast.cpp./test_dynamic_cast卡点2按值返回时切片现象Component factory() { return PlayerComponent(); }返回的对象已切片。原因返回值构造时调用基类拷贝构造切片发生在 return 语句。修复返回unique_ptrComponent或shared_ptrComponent。验证g-stdc11-O0-fno-elide-constructors test_return.cpp./test_return卡点3reserve不切片push_back/emplace_back切片现象reserve(10)正常push_back切片。换emplace_back也切片。原因reserve只分配内存不构造。emplace_back也执行拷贝/移动构造值语义不变。唯一解法components.push_back(std::make_uniquePlayerComponent())。验证对比push_back和emplace_back的汇编都调用基类拷贝构造。卡点4移动语义不避免切片现象components.push_back(std::move(PlayerComponent()))还是输出Component。原因移动构造也按类型大小工作。vectorComponent的移动构造参数是Component依然只移动基类部分。验证g-stdc11-O0test_move_slice.cpp./test_move_slice文末声明本题为公开工程技术难题不含任何企业商业秘密、未披露数据或专利陷阱。作者华夏之光永存标签#C #对象切片 #继承 #值语义 #多态 #内存布局 #拷贝构造 #unique_ptr #variant
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

数据结构知识点总结PDF:从ADT到复杂度,复习底稿全解析 2026/9/26 9:24:59

数据结构知识点总结PDF:从ADT到复杂度,复习底稿全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Spring AI 企业知识库问答实战:RAG 架构、PGVector 与混合检索 2026/9/26 9:24:59

Spring AI 企业知识库问答实战:RAG 架构、PGVector 与混合检索

1. 为什么选择 Spring AI 来做企业知识库问答企业知识库问答这个需求,这两年我接到的咨询特别多。几乎每一家有点规模的公司,内部都堆着成千上万份文档——产品手册、运维手册、合同模板、历史工单、会议纪要,散落在 Confluence、语雀、共享盘…

阅读更多 →
PVZTools原理与Win11适配:植物大战僵尸本地修改器技术解析 2026/9/26 9:24:52

PVZTools原理与Win11适配:植物大战僵尸本地修改器技术解析

1. 项目概述:这不是“外挂”,而是一套面向单机游戏的本地化辅助工具链“植物大战僵尸修改器PVZTools:3步搞定无限阳光与自动操作”——这个标题里藏着三个关键信号:PVZTools是工具名,无限阳光是核心功能之一&#xff0…

阅读更多 →
响应式页面智能整定:从手工调参到工程化交付的完整闭环 2026/9/26 9:24:52

响应式页面智能整定:从手工调参到工程化交付的完整闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
AI前沿 | 2026年9月14日:智谱 50 亿美元再融资 + 完全自训练体系 + GLM-5 系列迭代 2026/9/26 9:24:52

AI前沿 | 2026年9月14日:智谱 50 亿美元再融资 + 完全自训练体系 + GLM-5 系列迭代

AI前沿 | 2026年9月14日:智谱 50 亿美元再融资 完全自训练体系 GLM-5 系列迭代 📖 首屏导读 本教程配套付费专栏:《大模型工程师修炼手记》 19.9 元(AI 编程 Agent 实战 本文同主题系统课程) 《AI时代程序员的自我…

阅读更多 →
从定时任务到分布式调度:自研轻量级调度引擎AX的设计与实践 2026/9/26 9:24:52

从定时任务到分布式调度:自研轻量级调度引擎AX的设计与实践

先说一个可能很多人都有过的经历:业务量还小的时候,接到需求就是写个定时任务,扔到服务器上让它跑。等任务数量从几十个涨到几千个、上万个,执行时间、失败重试、集群环境下的重复调度、任务堆积这些乱七八糟的问题全冒出来之后&a…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉