Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

现代 C++ / Modern C++

1. 现代 C++ 总览:不是特性清单,而是表达能力 / Modern C++ Overview

2. 移动、转发与值类别:std::move 为什么不移动 / Move, Forward, and Value Categories

3. 类型推导、初始化与 constexpr / Type Deduction, Initialization, and constexpr

4. 模板、实例化与 SFINAE / Templates, Instantiation, and SFINAE

5. concept 与约束:把模板要求写在接口上 / Concepts and Constraints

6. 异常、RAII 与 noexcept:失败路径也要清理 / Exceptions, RAII, and noexcept

7. lambda、捕获与生命周期:回调不是免费午餐 / Lambdas, Captures, and Lifetime

8. callable wrapper 与 std::invoke:怎样保存和调用行为 / Callable Wrappers and std::invoke

9. 词汇类型与错误建模:让接口自己说话 / Vocabulary Types and Error Modeling

10. 协程与异步抽象:先理解状态机 / Coroutines and Async Abstractions

本页目录

词汇类型与错误建模:让接口自己说话 / Vocabulary Types and Error Modeling ​

词汇类型的意思是:大家看到这个类型,就能立刻理解一部分接口语义。

它们不是炫技容器,而是把“可能没有值”“几种结果之一”“只是观察不拥有”“失败带错误信息”写进类型。

1. 为什么返回特殊值不够好 ​

旧式接口经常这样写:

cpp
int find_user_id(const std::string& name);
1

约定:

text
返回 >= 0 表示找到
返回 -1 表示没找到
1
2

问题是:

text
调用者可能忘记检查 -1
-1 是否永远不可能是合法值?
接口声明本身看不出这个约定
失败原因无法表达
1
2
3
4

类型更好的做法是:

cpp
#include <optional>

std::optional<int> find_user_id(const std::string& name);
1
2
3

这行声明直接告诉你:

text
可能有 int。
也可能没有值。
1
2

2. optional<T> 表达可能没有值 ​

使用:

cpp
#include <optional>
#include <string_view>

std::optional<int> parse_int(std::string_view text) {
    if (text == "42") {
        return 42;
    }
    return std::nullopt;
}
1
2
3
4
5
6
7
8
9

调用:

cpp
if (auto value = parse_int("42")) {
    int x = *value;
}
1
2
3

optional<int> 可以处于两种状态:

text
+---------------------+
| has value           |
| int object inside   |
+---------------------+

+---------------------+
| no value            |
| no int object       |
+---------------------+
1
2
3
4
5
6
7
8
9

optional 适合表达:

text
查找可能没有结果
解析可能失败但不关心详细原因
缓存值可能还没生成
配置项可能不存在
1
2
3
4

不适合表达:

text
失败原因很重要
需要错误码或错误消息
有多种失败类型需要区分
1
2
3

3. variant 表达有限几种状态之一 ​

std::variant 可以保存几种类型之一:

cpp
#include <string>
#include <variant>

using Token = std::variant<int, double, std::string>;
1
2
3
4

它表达:

text
Token 当前要么是 int,
要么是 double,
要么是 string。
不能是别的。
1
2
3
4

访问:

cpp
#include <iostream>
#include <variant>

void print(Token const& token) {
    std::visit([](auto const& value) {
        std::cout << value << '\n';
    }, token);
}
1
2
3
4
5
6
7
8

std::visit 会根据当前真实替代类型调用 lambda。

variant 适合:

text
词法分析 token
配置值可能是 int/string/bool
状态机里的有限状态
消息类型有限且明确
1
2
3
4

它比 void* 或随便用基类指针更明确。

4. string_view 和 span 表达非拥有观察 ​

std::string_view:

cpp
void print_name(std::string_view name);
1

意思是:

text
我只看一段字符。
我不拥有这段字符。
1
2

std::span<int>:

cpp
void fill(std::span<int> values);
1

意思是:

text
我看一段连续 int。
我不拥有这段数组。
1
2

它们通常内部只保存:

text
pointer
length
1
2

所以生命周期非常重要。

危险例子:

cpp
#include <string>
#include <string_view>

std::string_view title() {
    std::string s = "hello";
    return s;
}
1
2
3
4
5
6
7

返回的 string_view 悬垂。

因为它观察的 std::string 已经析构。

5. expected 风格结果 ​

optional 只能表达有或没有。

如果你需要错误信息,可以使用 expected 风格。

C++23 有 std::expected。

C++17/20 项目里常见自定义:

cpp
template <class T, class E>
class Result;
1
2

接口形状:

cpp
Result<User, ParseError> parse_user(std::string_view text);
1

它表达:

text
成功时有 User。
失败时有 ParseError。
1
2

这比:

cpp
bool parse_user(std::string_view text, User& out, ParseError& error);
1

更容易让调用者看出结果结构。

6. 异常、optional、expected 怎么选 ​

没有一个永远正确的错误模型。

可以先按这个方向选:

text
失败是普通分支,只需要有/无
  -> optional

失败是普通分支,需要错误信息
  -> expected / Result<T, E>

结果是几种固定形态之一
  -> variant

当前层无法处理,失败不属于常规控制流
  -> exception
1
2
3
4
5
6
7
8
9
10
11

例子:

text
find_user(id)
  用户不存在是常见情况 -> optional<User>

parse_config(text)
  失败要指出行号和原因 -> Result<Config, ParseError>

read_token()
  token 可能是数字/标识符/字符串 -> variant

load_required_database()
  程序无法继续启动 -> exception 可能合适
1
2
3
4
5
6
7
8
9
10
11

7. 词汇类型是接口文档的一部分 ​

对比:

cpp
std::string get_name();
std::string_view get_name_view();
std::optional<std::string> find_name();
1
2
3

它们表达完全不同的语义:

text
std::string
  返回拥有的字符串值

std::string_view
  返回非拥有观察窗口,必须关心被观察对象生命周期

std::optional<std::string>
  可能没有名字;有的话返回拥有的字符串
1
2
3
4
5
6
7
8

类型本身就在提醒调用者该怎么用。

这就是词汇类型的价值。

8. 本篇总结 ​

常见词汇类型:

text
optional<T>
  可能有 T,也可能没有

variant<A, B, C>
  有限几种类型之一

string_view
  非拥有字符窗口

span<T>
  非拥有连续对象窗口

expected<T, E> / Result<T, E>
  成功有 T,失败有 E
1
2
3
4
5
6
7
8
9
10
11
12
13
14

设计接口时问:

text
调用者是否需要知道可能没有值?
失败原因是否重要?
返回值是拥有对象,还是观察窗口?
状态是否是有限几种形态之一?
类型能否替我表达这些语义?
1
2
3
4
5

现代 C++ 的一个方向,就是让接口少靠口头约定,多靠类型表达。

最后更新于:

Pager
上一篇8. callable wrapper 与 std::invoke:怎样保存和调用行为 / Callable Wrappers and std::invoke
下一篇10. 协程与异步抽象:先理解状态机 / Coroutines and Async Abstractions

持续记录,持续成长

Copyright © Tidenflow