在 C++17 之前,我们经常使用指针或特殊值来表示“缺失值”或“可选值”。随着 std::optional 的加入,代码变得更加语义化、类型安全且易于维护。下面通过一个实际案例,展示如何在一个简易的配置解析器中使用 std::optional。
1. 需求场景
我们需要解析一个 JSON 配置文件,其中包含若干可选字段,例如:
{
"host": "localhost",
"port": 8080,
"use_ssl": true,
"timeout": 30
}
host和port必须出现,否则解析失败。use_ssl是可选的,缺省为false。timeout是可选的,缺省为60秒。
我们希望在 C++ 代码中以强类型的方式表达这些约束。
2. 设计思路
- 使用 std::optional 表示可选字段
对于use_ssl和timeout,声明为 `std::optional ` 和 `std::optional`。 - 提供默认值
在解析完所有字段后,如果optional仍为空,使用业务默认值。 - 错误处理
对于必需字段缺失或类型不匹配,抛出异常或返回错误状态。
3. 示例代码
下面的代码演示了一个极简的解析器实现。为了简化,使用了 nlohmann::json 库来处理 JSON。
#include <iostream>
#include <optional>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// 配置结构体
struct Config {
std::string host; // 必需
int port; // 必需
std::optional <bool> use_ssl; // 可选
std::optional <int> timeout; // 可选
};
// 解析函数
Config parse_config(const std::string& json_str) {
json j = json::parse(json_str);
Config cfg;
// 必需字段
if (!j.contains("host") || !j["host"].is_string())
throw std::runtime_error("Missing or invalid 'host'");
cfg.host = j["host"].get<std::string>();
if (!j.contains("port") || !j["port"].is_number_integer())
throw std::runtime_error("Missing or invalid 'port'");
cfg.port = j["port"].get <int>();
// 可选字段
if (j.contains("use_ssl") && j["use_ssl"].is_boolean())
cfg.use_ssl = j["use_ssl"].get <bool>();
else
cfg.use_ssl = std::nullopt; // 明确标记为空
if (j.contains("timeout") && j["timeout"].is_number_integer())
cfg.timeout = j["timeout"].get <int>();
else
cfg.timeout = std::nullopt;
return cfg;
}
// 展示解析结果
void print_config(const Config& cfg) {
std::cout << "Host: " << cfg.host << '\n';
std::cout << "Port: " << cfg.port << '\n';
// 使用 optional 的语义
if (cfg.use_ssl.has_value())
std::cout << "Use SSL: " << std::boolalpha << cfg.use_ssl.value() << '\n';
else
std::cout << "Use SSL: (default) false\n";
if (cfg.timeout.has_value())
std::cout << "Timeout: " << cfg.timeout.value() << "s\n";
else
std::cout << "Timeout: (default) 60s\n";
}
int main() {
std::string raw_json = R"(
{
"host": "example.com",
"port": 443,
"use_ssl": true
}
)";
try {
Config cfg = parse_config(raw_json);
print_config(cfg);
} catch (const std::exception& e) {
std::cerr << "解析错误: " << e.what() << '\n';
}
return 0;
}
4. 代码说明
-
std::optional用法- `std::optional use_ssl;`
cfg.use_ssl.has_value()用来检查是否有值。cfg.use_ssl.value()访问实际值;若为空,调用value()会抛出异常。
-
默认值的提供
在print_config中,若 optional 为空,则使用业务默认值false(SSL)或60(timeout)。
也可以在解析完成后立即把默认值填充进去,避免后续每次使用都需要检查。 -
异常安全
使用try-catch捕获解析时抛出的异常,保证程序不因错误配置崩溃。
5. 小结
std::optional让“缺失值”成为一种可表达的类型,而不是隐式的nullptr或特殊值。- 在配置、命令行参数、数据库查询等场景中,使用
optional能提升代码的可读性和安全性。 - 结合 C++17 的强类型系统,可以在编译期捕捉更多错误,减少运行时异常。
通过以上示例,你可以在自己的项目中轻松替换掉传统的“缺失值”实现,享受更清晰、更安全的代码体验。