优先用 const t& 而非 const t,除非需判空;大型只读对象传 const t& 或 const t 避拷贝,小类型值传递更优;指针须确保所指对象生命周期足够长,否则引发悬空;移动语义或视图类型(如 std::span)常比裸指针更安全高效。

函数传参时什么时候该用指针而不是值传递
当参数是大型对象(比如 std::vector、自定义结构体超过 16 字节)且函数只读不修改时,传 const T* 或 const T& 能避免拷贝开销;但若函数需要修改原对象,且调用方明确允许修改,则传 T* 比传 T& 更清晰地表达“可为空”或“有意绕过引用语义”的意图。
常见错误是:对小类型(如 int、double、std::string_view)也盲目用指针——这反而增加解引用开销,还可能引入空指针风险。
- 值传递适合:POD 小类型(≤ 寄存器宽度)、
std::string_view、std::span - 指针传递适合:需表示“可选”语义(如
void process(int* opt_flag))、需在函数内重绑定目标(引用做不到)、或与 C API 交互必须用指针 - 优先考虑
const T&而非const T*,除非你真需要判空逻辑
如何避免指针传参引发的生命周期问题
指针本身不延长所指对象寿命,这是最常被忽略的性能陷阱:表面省了拷贝,实际因悬空指针导致未定义行为,调试成本远高于拷贝开销。
典型场景:std::string s = get_data(); func(&s[0]); —— 若 get_data() 返回临时 std::string,&s[0] 在分号后即失效。
- 永远检查指针来源:局部变量地址、函数返回的栈内存地址、
std::vector::data()是否在后续被resize()失效 - 对容器数据指针(如
vec.data()),确保容器生命周期长于指针使用期,或改用std::span<const t></const>显式约束范围 - 禁用裸指针跨作用域保存:如把
&local_obj存入全局缓存,改用std::shared_ptr或重构为所有权明确的设计
编译器能否优化掉不必要的指针解引用
现代编译器(GCC/Clang -O2 及以上)在多数情况下能将简单指针访问内联并消除冗余解引用,但前提是:指针不逃逸、无别名冲突、且上下文足够透明。
例如:int add_one(int* p) { return *p + 1; } 调用时若 p 来自已知地址(如数组元素),通常会被优化成直接计算;但若 p 来自未知函数返回值,编译器往往保守处理,保留解引用指令。
- 用
__restrict__(GCC/Clang)或[[clang::noalias]]提示编译器指针无别名,有助于生成更优代码 - 避免在循环中反复解引用同一指针:把
*ptr提前存到局部变量,尤其当ptr是类成员或全局变量时 - 结构体字段频繁访问?考虑用引用代替指针:
auto& field = obj->field;比每次obj->field更易被优化
替代方案:什么时候该放弃指针,改用移动语义或视图类型
如果函数目的是消费一个临时大对象(如解析一段 JSON 数据),传指针既不能转移所有权,又无法避免拷贝,此时 T&& 移动语义或 std::span/std::string_view 更合适。
例如:parse(std::vector<uint8_t>&& data)</uint8_t> 明确接管资源,比 parse(const uint8_t* data, size_t len) 更安全且零拷贝;而只读切片场景,std::string_view 比 const char* + size_t 更不易出错。
- 移动参数适用于:函数内部会 move 构造成员、或转交至其他 move-only 接口
-
std::span替代原始指针+长度组合,自带边界检查(debug 模式)和迭代器接口,且无额外运行时代价 - 慎用
std::unique_ptr传参:除非函数明确要接管资源,否则只是增加间接层,没节省任何东西
指针优化真正起效的前提,是它没有把问题从“拷贝开销”悄悄转移到“生命周期管理成本”或“可读性负债”上。越想省那几个字节的拷贝,越得盯紧对象在哪创建、在哪销毁、谁在用它。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











