强制要求裸指针的接口包括c风格api(如fopen、malloc)、系统调用(如read、mmap)及部分c++底层函数(如std::vector::data()),因类型不匹配而无法使用智能指针。

哪些接口强制要求 raw pointer
当你调用 C 风格 API(如 fopen、malloc、pthread_create)、系统调用(如 read、mmap)或某些 C++ 库的底层函数(如 std::vector::data() 返回 T*)时,参数或返回值类型就是 T*,无法传入 std::unique_ptr<t></t> 或 std::shared_ptr<t></t>。
这类场景下不是“想不想用”,而是“编译器不让你用”——类型不匹配会直接报错,例如:
FILE* fp = fopen("log.txt", "w");
// 不能写成:std::unique_ptr<file> fp = fopen(...); // 编译失败
</file>
- 必须用
nullptr初始化原始指针,避免野指针 - 若需将智能指针管理的资源交给 C 函数,用
ptr.get()获取裸地址,但要确保智能指针生命周期长于 C 函数调用 - 绝不能对
get()返回的指针调用delete——所有权仍在智能指针手里
std::unique_ptr 无法满足的性能敏感循环
在极少数对缓存友好性和指令吞吐有严苛要求的内核/音视频/高频数值计算场景中,编译器对原始指针的优化更激进。比如连续内存遍历:
int* p = arr; for (int i = 0; i <p>而 <code>std::unique_ptr<int></int></code> 的 <code>operator[]</code> 或 <code>get()[i]</code> 可能引入不可忽略的间接跳转或边界检查(即使未启用调试模式,某些 STL 实现仍保留轻量级断言)。</p>
- 仅当 profiler 明确指出该循环是瓶颈,且替换为原始指针后实测提升 ≥5% 才值得改
- 改完必须加注释说明“此处绕过 RAII 是因性能实测需求”,否则后续维护者极易误删
- 禁止在多线程共享数据结构中滥用此优化——竞态风险远高于几纳秒收益
表示“非拥有”语义的观察者指针
当你明确只想访问某对象、绝不参与其生命周期管理时,T* 比 std::shared_ptr<t></t> 或 std::weak_ptr<t></t> 更轻量、意图更清晰。典型例子:
- 类成员中缓存某个外部容器元素的地址(如
std::vector<widget>* owner_</widget>) - 回调函数中接收上下文指针(如
void callback(void* user_data)) - 树节点中指向父节点的
Node* parent_(避免循环引用)
关键判断标准:这个指针是否可能比它所指的对象活得更久? 如果答案是“否”,且你从不负责释放它,就该用原始指针;否则应选 std::weak_ptr 或引用。
注意:nullptr 是合法初始值,但一旦赋值,务必确认目标对象的生存期——悬空指针在这里不会触发任何运行时检查。
需要与 C ABI 兼容的结构体字段
定义用于跨语言交互(如 Rust、Python C API、Windows COM)或 mmap 内存映射的 POD 结构体时,字段类型必须是标准布局(standard-layout),而 std::unique_ptr 不是 POD 类型,不能出现在这种结构里。
struct ConfigHeader {
uint32_t version;
char* data_path; // OK: C ABI 兼容
// std::unique_ptr<char> data_path; // 错误:非 POD,破坏 ABI
int32_t timeout_ms;
};
</char>
- 这种结构体通常由 C 代码分配/释放,C++ 侧只读取或填充字段
- 字段若需动态内存,必须由使用者手动管理(如用
strdup分配,free释放) - 结构体内不得含虚函数、非 POD 成员、用户定义构造/析构函数
真正需要原始指针的地方其实很少——多数所谓“性能瓶颈”实际出在算法或 I/O,而非指针本身;而“兼容性”问题往往可通过封装层隔离。最容易被忽略的是语义混淆:把一个本该是 const T& 的只读访问,写成 T* 并初始化为 &x,既没带来好处,又开了悬空风险的口子。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











