首页/新闻资讯/正文详情

C++ 取地址符相关的默认函数

发布时间:2026/11/4 3:11:22 来源:云帆数科 栏目:资讯中心
C++ 取地址符相关的默认函数
前言这个标题的措辞比较含糊需要先界定范围。C 里与「取地址符」和「默认函数」相关的知识其实分属三块一是一元运算符可以被重载二是编译器为类隐式声明、也可以用 default显式恢复的特殊成员函数三是 C11 引入的std::addressof它能在被重载时仍然拿到对象的真实地址。三者常在同一段代码里相遇因此放在一起讲。典型误解有两个。第一以为obj永远返回对象地址——只要类重载了一元operatorobj调用的就是那个重载返回值可以是任何东西。第二以为写了~T() default;和没写一样——只要析构函数被「用户声明」编译器就不再隐式生成移动构造函数这个差异会直接影响性能与可移动性。本文先讲operator的重载规则与std::addressof的必要性再讲 default对特殊成员函数声明的影响最后用一个资源包装类把两者串起来。代码以 C17 为基准。一、一元可以被重载C 允许重载一元取地址运算符形式可以是成员函数也可以是非成员函数// 适用于 C17#include iostreamclass Tracked {public:Tracked* operator() {std::cout non-const address-of\n;return this;}const Tracked* operator() const {std::cout const address-of\n;return this;}};int main() {Tracked a;const Tracked b;Tracked* pa a; // 调用非 const 重载const Tracked* pb b; // 调用 const 重载std::cout (pa a) (pb b) \n;return 0;}成员形式的一元operator没有参数返回类型任意通常写成指针。非成员形式接收一个参数// 适用于 C17Tracked* operator(Tracked obj) { return obj; } // 注意内部再用 会递归上面这行如果真这么写函数体里的obj又会调用operator形成无限递归。正确写法是借助std::addressof或reinterpret_cast这正好引出下一节。需要区分一元与二元a b是按位与a是取地址两者是不同的运算符可以分别重载。重载了一元不会影响按位与反之亦然。二、std::addressof与它解决的问题std::addressof定义在memory中C11 起可用签名大致是// 适用于 C11 及以上template class TT* addressof(T arg) noexcept;它返回arg的真实地址即使T重载了一元operator。主流实现通过编译器内建函数如__builtin_addressof完成因此没有递归问题。// 适用于 C17#include memory#include iostreamclass Tracked {public:Tracked* operator() { return nullptr; } // 故意劫持返回空指针};int main() {Tracked a;Tracked* hijacked a; // 调用重载得到 nullptrTracked* real std::addressof(a); // 绕过重载拿到真实地址std::cout (hijacked nullptr) (real ! nullptr) \n;return 0;}这个例子把重载写成返回nullptr只是为了把差异放大到肉眼可见。结论是泛型代码中需要真实地址时一律用std::addressof。为什么标准库关心这件事因为std::vector、std::unique_ptr、std::function等模板内部需要取元素的地址来构造指针或做定位如果元素的类型重载了并返回了别的东西直接用就会得到错误结果。标准库实现因此在内部统一使用std::addressof。那么重载一元合理吗极少数场景有正当理由例如代理对象proxy希望proxy返回它所引用的真实对象地址或者智能指针希望sp返回内部指针。但这类设计会让代码对读者产生强烈误导现代 C 更倾向于用显式的get()方法表达而不是劫持运算符。除非有非常明确的理由不要重载一元。三、 default与隐式声明的特殊成员函数C 的类有六个特殊成员函数默认构造、拷贝构造、拷贝赋值、移动构造、移动赋值、析构。编译器会在满足条件时隐式声明它们。 default的作用是「显式要求编译器生成默认实现」 delete则是「明确禁用」。// 适用于 C17#include string#include vectorclass Record {public:Record() default;Record(const Record) default;Record operator(const Record) default;Record(Record) default;Record operator(Record) default;~Record() default;private:std::string name_;std::vectorint values_;};对一个只含std::string与std::vector的类其实一个都不用写——编译器隐式生成的版本已经正确这就是「零规则」rule of zero。 default在这里的价值是显式表达意图同时保持函数「非用户提供」not user-provided从而不破坏类型的三 trivial 性与聚合性质。关键细节在于「用户声明」与「用户提供」的区别写法是否用户声明是否用户提供对隐式移动的影响不写否否正常隐式生成~Record() default;类内首次声明是否抑制隐式移动构造/赋值~Record() {}是是抑制隐式移动且析构非平凡~Record();类外Record::~Record() default;是是抑制隐式移动也就是说只要你在类内声明了析构函数哪怕写成 default编译器就不会再隐式声明移动构造函数和移动赋值运算符。此后对Record做std::move实际调用的是拷贝构造——因为移动版本不存在重载决议回退到const T的拷贝构造。这是一个非常隐蔽的性能陷阱代码看起来用了移动实际发生了深拷贝。如果确实需要自定义析构例如要释放裸资源就必须把移动构造和移动赋值也显式 default或自行实现这就是「五规则」rule of five。更好的做法是把资源交给std::unique_ptr等 RAII 成员让析构保持隐式从而回到零规则。 default还可以写在类外但那时函数被视为「用户提供」语义与类内首次声明不同。日常编码建议一律写在类内首次声明处。C20 起 default还可用于默认化的比较运算符例如bool operator(const T) const default;与auto operator(const T) const default;编译器会按成员逐级生成比较。这属于 C20 特性使用前需确认工具链支持。四、把两者放进一个例子下面这个类同时涉及取地址重载与默认函数它包装一个堆上的整数提供显式的get()并且不重载——这是推荐做法。// 适用于 C17#include memory#include iostreamclass IntHolder {public:IntHolder() default;explicit IntHolder(int value) : value_(new int(value)) {}// 析构由 unique_ptr 负责无需手写保持零规则int value() const { return value_ ? *value_ : 0; }void reset(int value) { value_.reset(new int(value)); }private:std::unique_ptrint value_;};int main() {IntHolder a(7);IntHolder b std::move(a); // 移动构造隐式可用std::cout b.value() \n;IntHolder* p std::addressof(b); // 即使将来重载 这行仍然正确std::cout (p ! nullptr) \n;return 0;}IntHolder没有声明析构、拷贝、移动中的任何一个编译器隐式生成移动构造与移动赋值因为成员unique_ptr可移动拷贝构造被隐式删除因为unique_ptr不可拷贝。std::move(a)因此走的是移动构造a进入「有效但未指定」状态此后不应再读取它的值。std::addressof(b)在这里与b等价因为IntHolder没有重载写成addressof是为了让代码在未来加入重载时仍然正确也让读者明确知道这里要的是真实地址。常见坑点坑点一以为obj一定返回对象地址。// ❌ 错误类重载了 operator返回值可能不是 thisSomeProxy p;void* addr p;// ✅ 正确需要真实地址时用 std::addressofvoid* addr std::addressof(p);坑点二在重载的operator内部再用造成递归。// ❌ 错误函数体里的 obj 再次调用自身Tracked* operator(Tracked obj) { return obj; }// ✅ 正确用 std::addressof 或内建手段Tracked* operator(Tracked obj) { return std::addressof(obj); }坑点三随手重载一元以「美化」接口。// ❌ 错误让 obj 返回内部指针泛型代码拿到错误地址class Smart { public: int* operator() { return raw_; } };// ✅ 正确提供显式的 get()保持 的常规语义class Smart { public: int* get() { return raw_; } };坑点四声明了 default析构却以为移动仍然隐式生成。// ❌ 错误~T() default 抑制了隐式移动std::move 实际走拷贝class T {public:~T() default;std::vectorint data;};// ✅ 正确需要移动就显式声明或干脆不写析构class T {public:T(T) default;T operator(T) default;~T() default;std::vectorint data;};坑点五把 default写在类外误以为与类内等价。// ❌ 错误类外默认化属于「用户提供」语义不同class T { public: T(); };T::T() default;// ✅ 正确在类内首次声明处默认化class T { public: T() default; };坑点六用 delete禁用拷贝后忘记提供移动。// ❌ 错误拷贝被删除移动也未声明对象无法转移class Handle {public:Handle(const Handle) delete;Handle operator(const Handle) delete;};// ✅ 正确同时声明移动操作class Handle {public:Handle(const Handle) delete;Handle operator(const Handle) delete;Handle(Handle) default;Handle operator(Handle) default;};坑点七对const对象使用std::move期待移动。// ❌ 错误const 对象只能绑定到 const T退化为拷贝const std::string s hello;std::string t std::move(s);// ✅ 正确去掉 const或接受这是一次拷贝std::string s hello;std::string t std::move(s);总结主题要点一元可重载成员形式无参非成员形式接收一个参数std::addressofC11绕过重载拿真实地址泛型代码应默认使用 default显式要求编译器生成类内首次声明处使用用户声明析构会抑制隐式移动构造与移动赋值零规则用 RAII 成员不手写特殊成员函数五规则一旦自定义析构/拷贝/移动之一通常五个都要考虑「取地址符」与「默认函数」看似不相关实际都指向同一个原则不要依赖隐式行为也不要用运算符重载去制造惊喜。需要真实地址就写std::addressof需要特殊成员函数就显式写出并想清楚它对其他成员的影响代码的可预测性会好很多。

相关推荐

SysML/UML系统工程建模:SYSMOD流程、需求追踪与模型校验
SysML/UML系统工程建模:SYSMOD流程、需求追踪与模型校验

简介:《Systems Engineering with SysML/UML》面向系统工程从业者、软件架构师及高校相关专业学习者,聚焦如何以 OMG 标准化视觉建模语言完成从需求到验证的完整建模流程,帮助读者解决软件建模方法难以覆盖复杂系统全生命周期的问题。全书以 … · 2026/11/4 3:11:22

智能系统开发中的不可能三角:效率、精度与泛化的平衡
智能系统开发中的不可能三角:效率、精度与泛化的平衡

1. 智能系统的核心矛盾解析"智能中的不可能三角"这个概念最早源于分布式系统领域的CAP定理,但在人工智能时代被赋予了新的内涵。简单来说,它描述的是智能系统开发过程中三个相互制约的核心要素:计算效率、模型精度和泛化能力。就像… · 2026/11/4 3:11:22

Vue Router与原生JS路由导航的核心差异与实践
Vue Router与原生JS路由导航的核心差异与实践

1. 前端路由导航的本质差异现代前端开发中,路由导航是构建单页面应用(SPA)的核心机制。原生JavaScript和Vue Router虽然都能实现页面跳转,但底层原理和适用场景存在显著区别。最近在重构一个老项目时,我不得不深入比较两者的实现细节&#xf… · 2026/11/4 3:10:22

AVP-Loc泊车定位方案:PCA、ICP与ESKF融合实战
AVP-Loc泊车定位方案:PCA、ICP与ESKF融合实战

1. 从泊车场景说起:AVP-Loc到底在解决什么问题自动代客泊车(AVP,Automated Valet Parking)这个场景,和高速NOA、城市NOA有一个本质区别:它的终点是一个固定的、已知的、但精度要求极高的位置。高速上你偏个… · 2026/11/4 13:26:17

AVP-Loc地库定位实战:PCA、ICP与ESKF多传感器融合调优
AVP-Loc地库定位实战:PCA、ICP与ESKF多传感器融合调优

1. 从泊车场景说起:AVP-Loc到底在解决什么问题自动代客泊车(AVP,Automated Valet Parking)这个场景,说白了就是让车自己去找车位、自己停进去。听起来简单,但真正落地的时候,最要命的问题不是控… · 2026/11/4 13:26:17

自动代客泊车定位方案AVP-Loc:PCA与ICP在BEV下的工程实践
自动代客泊车定位方案AVP-Loc:PCA与ICP在BEV下的工程实践

1. 从泊车场景说起:AVP-Loc到底在解决什么问题 自动代客泊车(Automated Valet Parking,AVP)这个场景,说白了就是让车自己在地库里找车位、停进去,或者你下车走人,车自己去找位置。听起来很酷&am… · 2026/11/4 13:26:17

POA-GRNN混合模型在时间序列预测中的优化应用
POA-GRNN混合模型在时间序列预测中的优化应用

1. 项目背景与核心价值 时间序列预测在金融、气象、工业控制等领域具有广泛应用价值。传统方法如ARIMA、指数平滑等虽然成熟,但在处理非线性、非平稳数据时表现有限。广义回归神经网络(GRNN)因其强大的非线性拟合能力成为时间序列建模的有力工具,但其平滑… · 2026/11/4 13:26:17

Claude、Codex、WorkBuddy对比:为什么熟人推荐AI工具总翻车?
Claude、Codex、WorkBuddy对比:为什么熟人推荐AI工具总翻车?

1. 从一次失败的安利说起:为什么熟人推荐AI工具总是翻车我干这行十来年,见过太多类似的场景:自己用某个AI工具用得飞起,兴冲冲推荐给身边人,结果对方试了两下就丢到一边,再也不碰。标题里这位老哥的遭遇&am… · 2026/11/4 13:26:17

Oracle EBS ASCP高级供应链计划实战:多工厂手机制造案例配置与验收
Oracle EBS ASCP高级供应链计划实战:多工厂手机制造案例配置与验收

简介:这份文档面向 Oracle EBS 供应链与计划模块的实施顾问、ERP 运维人员及学习高级计划排程的从业者,以一家手机制造企业的完整案例为线索,系统梳理 ASCP 方案的搭建与测试流程。内容从物料定义与组织分配、BOM 与工艺路线设置,… · 2026/11/4 13:25:15

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

AI辅助技术栈评估:提升研发决策效率60%
AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17

从生成Token到直接决策:Jev如何重塑AI Agent工程实践
从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17

32G统一内存跑大模型:内存账、TPS实测与端云分流
32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码