【C++17】使用std::optional优雅的定义函数返回值

众所周知,我们经常会用到带返回值的函数,并且需要利用到返回值做逻辑判断。但仅对返回值做处理,多数情况下不便于同时获取函数执行结果与函数的状态。例如返回null值,返回错误值,返回正确值。

我们可以使用C++17 std::optional,来优雅的处理此情况,直接表达「有值」或「无值」的意图,避免魔数(如 -1、nullptr)。下面列举常见用法以及规避使用的情况。

1. std::optional 诞生的意义

std::optional 的引入解决了在函数返回值为“无值”或“错误值”时的两大问题:

  • 明确性:当函数返回一个可能没有值的结果时,传统做法是返回一些哨兵值(如 -1nullptr 或空字符串)。这些值很容易被误用,也不清晰地表达该值是否有效。std::optional 明确表示返回的值要么有效(包含一个值),要么无效(没有值)。

  • 类型安全:使用 std::optional 可以避免潜在的类型问题。例如,返回一个整数 -1 来表示错误,这样就可能会与有效的 -1 数据值冲突。而使用 std::optional<int> 可以更清晰地表示返回值是否存在。

2. std::optional 常见用法

基本用法
#include <iostream>
#include <optional>

std::optional<int> divide(int a, int b) {
    if (b == 0) {
        return std::nullopt; // 返回无效值(没有结果)
    } else {
        return a / b; // 返回有效值
    }
}

int main() {
    std::optional<int> result = divide(10, 2);
    if (result) {
        std::cout << "Result: " << *result << std::endl; // 使用解引用运算符 *
    } else {
        std::cout << "Division by zero!" << std::endl;
    }

    result = divide(10, 0);
    if (result) {
        std::cout << "Result: " << *result << std::endl;
    } else {
        std::cout << "Division by zero!" << std::endl;
    }

    return 0;
}
常用操作
  • 初始化:使用 std::optional 的构造函数来创建可选值。

    std::optional<int> opt1 = 42; // 有值
    std::optional<int> opt2;      // 无值,等价于 std::nullopt
    
  • 判断是否有值:使用 operator bool() 来判断是否有值,或者使用 has_value() 方法。

    if (opt1) { /* 有值 */ }
    if (!opt2) { /* 无值 */ }
    
  • 访问值:使用解引用运算符 *value() 方法访问值。value() 如果没有值会抛出 std::bad_optional_access 异常。

    if (opt1) {
        std::cout << *opt1 << std::endl; // 解引用访问
    }
    std::cout << opt1.value() << std::endl; // 使用 value()
    
  • 默认值:使用 value_or 方法,在没有值时返回一个默认值。

    int value = opt2.value_or(100);  // 如果 opt2 没有值,返回 100
    
  • 重置值:使用 reset() 方法来清除 optional 内部的值。

    opt1.reset();  // 清除 opt1 的值
    
链式调用与转换
std::optional<int> add_one(std::optional<int> opt) {
    return opt ? std::optional<int>(*opt + 1) : std::nullopt;
}

std::optional<int> multiply_by_two(std::optional<int> opt) {
    return opt ? std::optional<int>(*opt * 2) : std::nullopt;
}

int main() {
    std::optional<int> value = 5;
    std::optional<int> result = add_one(value).flat_map(multiply_by_two);
    if (result) {
        std::cout << "Result: " << *result << std::endl;
    } else {
        std::cout << "No valid result" << std::endl;
    }
}

在上面的例子中,flat_map 被用来进行链式调用,并且保持了 optional 的状态。

3. 常见坑和注意事项

坑 1: 不小心解引用无值的 optional

解引用一个无值的 optional 会导致未定义行为。为了避免这种情况,使用 has_value() 或者 operator bool() 来确保值是有效的。

std::optional<int> opt; // 无值
if (opt) {
    std::cout << *opt << std::endl; // 不会出错
} else {
    std::cout << "No value" << std::endl;
}
坑 2: value() 会抛出异常

value() 方法在 optional 没有值时会抛出 std::bad_optional_access 异常。所以,当你不确定 optional 是否有值时,使用 value_or() 或者先检查 has_value()

std::optional<int> opt;
// throw std::bad_optional_access
try {
    std::cout << opt.value() << std::endl; 
} catch (const std::bad_optional_access&) {
    std::cout << "Caught exception: No value" << std::endl;
}

// 更安全的使用方式
std::cout << opt.value_or(42) << std::endl;  // 如果没有值,返回默认值 42
坑 3: optional 不支持 null

std::optional 只能表示一个“有值”或“无值”状态,并不能直接用于表示指针的“空指针”或其他“无效”的指针情况。如果要处理指针类型的“空”值,通常仍然使用 nullptr

std::optional<std::string> opt1; // 这是“没有值”
std::string* ptr = nullptr;       // 这是空指针
坑 4: 赋值和拷贝

optional 是有值时才会拷贝其值,否则为“无值”状态。在赋值时需要注意:

std::optional<int> opt = 42;
std::optional<int> opt2 = opt;  // 拷贝值
opt2.reset();  // opt2 变为无值
std::optional<int> opt3;        // opt3 没有值
opt3 = opt2; // opt3 也将是无值状态
坑 5: 和 std::nullopt 一起使用时的误解

std::nullopt 是一个特殊的值,表示 optional 没有值。它不能与具体的值一起使用,且必须与 std::optional 一起使用。

std::optional<int> opt = std::nullopt; // 正确
std::optional<int> opt2 = 5;           // 正确
// std::optional<int> opt3 = std::nullopt = 5; // 错误,不能与值一起使用
坑 6: 性能问题

optional 本身是一个小对象,但它的内部实现可能包含动态内存分配或多次拷贝,尤其在存储大对象时会产生一些性能开销。对于性能敏感的应用,要特别注意这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值