现代 C++(C++11 及以后)强烈推荐优先使用引用(references)而不是裸指针(raw pointers),核心原因可以用一句话概括:
引用在表达意图、安全性、可读性和现代设计模式上都比裸指针更优秀,只有在“必须”使用指针的少数场景才用指针。
下面从最实际、最常被问到的角度,逐条拆解为什么现代 C++ 更偏爱引用:
1. 引用表达更清晰的“必须存在”的语义(no null guarantee)
| 特性 | 引用 (T&) / const T& | 裸指针 (T*) | 谁更清晰? |
|---|---|---|---|
| 是否可以为 null | 不可能(编译期保证) | 可以(nullptr) | 引用 |
| 调用处语义 | “这个对象一定存在,我要操作它” | “这个对象可能不存在,我要小心检查” | 引用 |
| 阅读代码时的假设 | 不用写 if (p != nullptr) | 几乎总是要写 null 检查 | 引用 |
现代 C++ 哲学:函数签名应该尽可能表达约束和意图。
void process(const Widget& w)→ “我需要一个存在的 Widget 来操作”void process(const Widget* w)→ “我可能接受没有 Widget 的情况”
用引用能消除大量 null 检查代码,减少 bug(null 解引用是经典崩溃原因)。
2. 语法更简洁、可读性更高(无 * 和 & 的噪音)
// 指针风格(传统/繁琐)
void print(const std::string* s) {
if (s) std::cout << *s << "\n"; // 每次都要解引用 + 检查
}
// 引用风格(现代/干净)
void print(const std::string& s) {
std::cout << s << "\n"; // 直接用,像本地变量
}
- 没有
*和&的到处飞 → 代码更干净 - 函数体内不用每次都写
*ptr或ptr-> - 模板元编程中,引用能更好地配合值语义(value semantics)
3. 引用天然支持 C++ 的值语义和 move 语义
引用(尤其是 const 引用)可以绑定到临时对象,并延长其生命周期(lifetime extension),这是指针做不到的:
void log(const std::string& msg); // 可以安全传临时对象
log("Hello " + std::to_string(42)); // OK,临时 string 被延长生命周期
// 指针做不到(临时对象立即析构)
void log(const std::string* msg); // 危险!调用后临时对象已销毁
log(&("Hello " + std::to_string(42))); // 未定义行为!
现代 C++ 中,const & 是传递大对象(string、vector 等)的首选方式,既避免拷贝,又安全。
4. 引用在成员变量和 RAII 设计中更安全
class Engine {
// 指针方式(容易忘记初始化)
Renderer* renderer = nullptr;
// 引用方式(必须在初始化列表中绑定)
Renderer& renderer;
public:
Engine(Renderer& r) : renderer(r) {} // 编译期强制初始化
};
- 引用成员变量强制在构造函数初始化列表中初始化,杜绝“未初始化”状态
- 指针成员容易出现“悬垂指针”或“忘记 delete”
5. 现代 C++ 生态全面拥抱“少用裸指针”
| 场景 | 现代 C++ 首选写法 | 为什么不用裸指针? |
|---|---|---|
| 函数参数(只读) | const T& | 安全、无 null、无拷贝 |
| 函数参数(可修改) | T& | 清晰意图 |
| 返回值(可选值) | std::optional<T&> 或 T*(慎用) | 通常用 optional 更好 |
| 拥有所有权 | std::unique_ptr<T> / std::shared_ptr<T> | 自动管理生命周期 |
| 观察者/回调(不拥有) | T* 或 std::weak_ptr<T> | 少数必须用指针场景 |
| 数组/迭代器 | span<T> / std::ranges | 取代 T* + size |
核心口诀(CppCoreGuidelines 经典建议):
F.7: For general use, take T* or T& arguments rather than smart pointers
F.20: Prefer to use references when you can, pointers when you must
什么时候仍然必须用指针?(少数例外)
- 需要“可重置”(reseating)的场景 → 指针可以指向另一个对象,引用不行
- 需要表达“可选”(可能为空) →
T*或std::optional<T> - 与 C 风格 API 交互(很多遗留库返回/接受
T*) - 实现低级数据结构(如链表节点、自定义分配器)
- 性能极致场景(极少数),指针有时更灵活
总结:现代 C++ 的指针 vs 引用优先级排序
- 值类型 / 小对象 → 传值(C++17+ 拷贝省略优化很好)
- 大对象 / 只读 →
const T& - 需要修改 →
T& - 需要表达“可选 / 可为空” →
T*(或更好用std::optional<T&>/std::weak_ptr) - 需要所有权 →
unique_ptr/shared_ptr - 极少数低级场景 → 裸指针
一句话记住:
在现代 C++ 中,引用是默认选择,裸指针是“不得已而为之”的工具。
如果你当前代码中大量使用裸指针,想改成更现代的写法,或者想看具体场景的对比代码(比如函数参数、容器元素、回调等),可以直接告诉我,我给你针对性例子。