C++17 新特性:std::optional —— 优雅处理函数返回
在实际开发中,我们经常会遇到这样的需求:
一个函数有时能返回有效数据,有时返回失败。
那么,函数该怎么告诉调用者“我失败了”?
以前我们可能会这样写:
传统写法一:返回空字符串
#include <iostream>
#include <fstream>
std::string ReadFileAsString(const std::string& filePath)
{
std::ifstream stream(filePath);
// 输入文件流
// 如果文件打开成功 又或者无法打开 要处理它
if (stream)
{
std::string result; // 用于存储从文件中读取的内容
// read_file
stream.close();
return result;
}
// 如果不成功
return std::string();
// 返回空字符串对象 利用std::string的默认构造函数 等价于std::string("")
}
然后在调用时:
int main()
{
std::string data = ReadFileAsString("data.txt");
if (data != "")
// 但是假如文件就在那里 它是空的 但它是有效的 我们需要一种方式确定它是否有效
{
// 文件读取成功
}
问题在于:
如果文件本身就是空的,我们就无法区分“文件是空的”和“文件没打开成功”。
传统写法二:用引用参数输出成功标志
#include <iostream>
#include <fstream>
std::string ReadFileAsString(const std::string& filePath, bool& outSuccess)
{
std::ifstream stream(filePath);
if (stream)
{
std::string result;
// read_file
stream.close();
outSuccess = true; // 表示读取成功
return result;
}
outSuccess = false; // 表示读取失败
return std::string();
}
调用方式:
int main()
{
bool fileOpenSuccessfully;
std::string data = ReadFileAsString("data.txt", fileOpenSuccessfully);
if (fileOpenSuccessfully)
{
std::cout << "文件读取成功!内容如下:\n" << data << std::endl;
}
else
{
std::cout << "文件读取失败!" << std::endl;
}
}
这种方式虽然能区分成功与失败,但函数签名变得复杂,需要额外的布尔参数,而且语义也不够清晰。
使用 std::optional 改进
C++17 引入了 std::optional,用来表示“一个可能存在、也可能不存在的值”。
它就像一个可空容器,里面可能装着一个 T 类型的值,也可能什么都没有。
基本示例
#include <iostream>
#include <fstream>
#include <optional>
std::optional<std::string> ReadFileAsString(const std::string& filePath)
{
std::ifstream stream(filePath);
if (stream)
{
std::string result;
// read_file...
stream.close();
return result; // 有值的情况
}
return {}; // 无值
}
int main()
{
std::optional<std::string> data = ReadFileAsString("data.txt");
if (data.has_value()) // 或者直接 if (data)
{
std::cout << "File read successfully!\n";
// 取出其中的值
std::string& str = *data; // 解包操作
// 或者
// std::string str = data.value();
}
else
{
std::cout << "File could not be opened!\n";
}
}
std::optional 的常用操作
-
std::optional<T>:定义一个可能包含 T 类型数据的对象 -
return {}:返回“没有值” -
.has_value():判断是否包含有效值 -
operator bool():可直接写 if (opt) -
*opt:解包取出值 -
.value():获取值(若无值会抛异常) -
.value_or(default):获取值或默认值
使用 std::optional 取出值
当我们调用 ReadFileAsString 并得到一个 std::optional<std::string> 时,注意:data 不是字符串本身,而是一个“可能装着字符串”的容器。
std::optional<std::string> data = ReadFileAsString("data.txt");
此时有两种情况:
-
文件读取成功 →
data里装着一个std::string -
文件读取失败 →
data是空的,没有任何值
解包 optional 中的值
如果我们确定 data 里有值(比如在 if (data) 判断之后),
就可以取出内部的字符串对象,方法有两种:
std::string& str = *data;
或
std::string str = data.value();
这里的 *data 并不是“指针解引用”,
而是 optional 类型的“解包”操作 —— 取出它内部存放的那个实际对象。
也就是说:
-
data是一个std::optional<std::string> -
*data是其中真正的std::string
因此,data 不能直接当作字符串使用,它只是一个包装容器。
只有在解包后(用 *data 或 data.value())才能得到真正的字符串对象。
示例:
if (data)
{
std::cout << "File content:\n";
std::cout << *data; // 解包后打印
}
else
{
std::cout << "File could not be opened.\n";
}
示例:value_or
std::optional<std::string> data = ReadFileAsString("data.txt");
// 如果 data 有值,返回其内容;
// 否则返回 "File not found"
std::string content = data.value_or("File not found");
std::cout << content << std::endl;
示例:基本类型
std::optional<int> count;
int c = count.value_or(100); // 如果 count 没值,默认返回 100
关于文件路径
文件路径是相对于项目的“工作目录”的。
如果你的 data.txt 文件放在项目根目录(即 .vcxproj 文件所在目录),可以直接:
ReadFileAsString("data.txt");
如果放在 src/ 目录下,则应写:
ReadFileAsString("src/data.txt");
优点总结
使用 std::optional 具有以下优点:
-
语义清晰,能明确表达“值是否存在”
-
函数接口简洁,无需额外的输出参数
-
使用安全,可以用 value_or 提供默认值
-
与现代 C++ 风格一致,易于维护
总结
std::optional<T> 是一个模板类,用来表达“值可能存在,也可能不存在”。
它可以替代传统的“返回空对象”或“额外的 bool 参数”。
optional 并不是指针,但你可以通过 *data 或 data.value() 访问内部对象。
当值不存在时,可以使用 .value_or(default) 提供安全的默认返回。
当一个函数的返回值“不一定有意义”时,用 std::optional 让代码表达力更强,逻辑更清晰。
更多推荐


所有评论(0)