std::span 能安全替代 T* + size_t,因其将指针与长度绑定为不可分割的视图,不拥有数据、避免拷贝,且在编译期或运行时捕获尺寸错误与生命周期问题。
为什么
std::span
能安全替代
T*
+
size_t
参数对
裸指针加长度的组合在函数接口中极易引发越界、悬空或生命周期不匹配问题,
std::span
把二者绑定为一个不可分割的视图对象,且不拥有数据——既避免拷贝开销,又强制调用方明确传递有效范围。
常见错误现象:
process(arr, n)
中
n
传错、
arr
已析构、
arr
实际长度小于
n
;而
process(std::span(arr, n))
在编译期就能捕获多数尺寸不匹配(如数组字面量推导),运行时构造失败也会触发断言(取决于实现)。
std::span
默认要求元素类型可平凡复制(trivially copyable),对非 POD 类型需显式使用
std::span<:byte>
或配合
reinterpret_cast
传参优先用
std::span
表达只读意图,避免意外修改原容器
不要从局部数组取
std::span
并返回——生命周期仍由原数组决定,
span
不延长生存期
std::expected
比
std::optional
或异常更适合哪些错误场景
std::expected
明确区分「成功值」和「可预期的错误原因」,适用于错误可分类、需透传上下文、且不希望用异常打断控制流的场合(如解析配置、系统调用封装、异步 I/O 结果)。
对比:
std::optional
只能表达“有/无”,无法携带错误信息;抛异常在性能敏感路径或禁用异常的环境(嵌入式、游戏引擎)中不可行。
立即学习
“
C++免费学习笔记(深入)
”;
C函数速查手册(CHM版)
C函数速查手册(CHM版)
下载
错误类型
E
应轻量且可复制,推荐用
enum class
(如
enum class parse_err { invalid_format, out_of_range }
)而非
std::string
不要把
std::expected
当作万能错误容器——若错误不可恢复或属编程缺陷(如空指针解引用),仍应使用断言或异常
链式调用时可用
.and_then()
替代嵌套
if (e.has_value())
,但注意 GCC 13 前部分实现未完全支持 C++23 的扩展操作符
何时该用
std::variant
而不是虚函数或多态指针
当类型集合固定、数量有限、且操作集中在单个函数内(如序列化、比较、格式化),
std::variant
比虚函数更高效:无虚表查表、无动态内存分配、编译期确定布局;它天然支持访问者模式,也更容易做 constexpr 计算。
典型误用:
std::variant<:unique_ptr>, std::shared_ptr>
—— 这反而引入间接和堆分配,失去
variant
的优势。
成员类型必须互不相同;若需多个同类型(如多个
int
),改用带 tag 的结构体包装
访问时优先用
std::visit([](const auto& v) { ... }, var)
,避免重复写
std::holds_alternative
分支
std::monostate
可作默认初始化占位符,但注意它不参与相等比较(
std::variant{}
和
std::variant{std::monostate{}}
不等)
裸指针还没彻底淘汰?这些边界情况仍需谨慎处理
现代 C++ 并未禁止裸指针,而是限制其使用范围:
new
/
delete
配对、C API 交互、低层内存管理(如自定义 allocator)、以及某些模板元编程技巧中仍会见到。关键在于——裸指针只应出现在你**明确承担全部生命周期责任**的地方。
容易被忽略的点:
std::span
和
std::string_view
都不保证底层内存对齐;若对接 SIMD 或硬件寄存器映射,仍需用
alignas
+ 原始指针,并手动校验地址。
与 C 函数交互时,用
std::data(span)
取指针比
&span[0]
更安全(前者对空 span 返回合法空指针,后者未定义)
禁用
隐式转换
:给接受裸指针的旧接口封装一层,参数强制为
std::span
或
std::string_view
,内部再转出指针
静态分析工具(如 clang-tidy 的
cppcoreguidelines-pro-bounds-pointer-arithmetic
)可帮你定位残留的危险指针算术