背景
在实际的软件开发过程中我们经常会遇到无效值的情况。例如,函数并不是总能返回有效值,很多时候即使函数正确执行,但其结果却不是合理的值。如果用数学语言来解释,这种情况就是返回值位于函数解空间之外。
求一个数的倒数、在实数域内开平方、在字符串中查找子串,它们都可能返回无效值。有些无效返回的情况下可以用抛出异常的方式来通知用户,但在某些情况下,这样代价很高或不允许抛出异常,这时必须要以某种合理的、高效的方式通知用户。
表示无效值最常用的做法是增加一个"哨兵"的角色,它位于解空间之外,如 NULL、-1、EOF、string::npos、vector::end()等。但这些做法不是通用的,而且很多时候不存在解空间之外的"哨兵"。另外一种方法是使用 pair<T, bool>的方式,用一个额外的bool值来标记值是否有效,标准容器的 set.insert()就是这样的。
optional 使用容器语义,包装了"可能产生无效值"的对象,实现了未初始化的概念,为这种无效值的情况提供了更好的解决方案。optional 已经被收入 C++17 标准。
optional 位于名字空间 boost,需要包含头文件<boost/optional.hpp>:
cpp
#include <boost/optional.hpp>
using namespace boost;
1. 类摘要
optional 库首先定义了常量 boost::none,表示未初始化,明确了无效值:
cpp
class none_t {}; //定义类型 none_t,具体形式依据条件编译不同而不同
const none_t none = ... ; //定义常量 none,具体形式依据条件编译不同而不同
optional 库的核心类是 optional,它很像是个仅能存放一个元素的容器,实现了未初始化的概念:如果元素未初始化,那么容器就是空的,否则,容器内的值就是有效的,已经初始化的值。
optional 的类摘要如下:
cpp
template<class T>
class optional
{
public:
optional(); //构造函数
optional(none_t);
optional(T const& v);
optional(bool condition, T v);
optional& operator=(T const& rhs); //赋值操作符
template<class... Args>
void emplace(Args... && args); //就地创建
T* operator->(); //重载操作符
T& operator*();
T& get(); //访问值
T* get_ptr();
T& value(); //访问值,可能抛出异常
T const& value_or(T const& default) const;
template <typename F>
T value_or_eval(F f) const;
explicit operator bool() const; //显式bool转型
bool operator!() const; //bool测试
};
optional 的真实接口很复杂,因为它要能够包装任何的类型,但实际的接口还是比较简单并且易于理解的,接下来将进行详细说明。
2. 操作函数
optional 的模板类型参数 T 可以是任何类型,就如同一个标准容器对元素的要求,并不需要 T 具有默认的构造函数,但 T 必须是可拷贝构造的,因为 optional 需要在内部拷贝值。
有很多方式可以创建 optional 对象,具体如下。
-
用无参的 optional() 或 optional(boost::none) 构造一个未初始化对象。
-
optional(v) 构造一个已初始化的 optional 对象,内部拷贝 v 的值。如果模板类型为 T&,那么 optional 内部持有对引用的包装。
-
optional(condition,v) 根据条件 condition 来构造 optional 对象,如果条件成立(true),则初始化为 v,否则为未初始化。
-
optional 支持拷贝构造和赋值操作,可以从另一个 optional 对象构造。
-
emplace() 是一个特殊的赋值函数,它可以使用参数就地创建对象,避免了构造后再拷贝的代价。
-
想让一个 optional 对象重新恢复到未初始化状态,可以向此对象赋 none 值。
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/optional.hpp>
using namespace std;
int main()
{
cout << "Start" << endl;
boost::optional<int> op1;
boost::optional<int> op2(boost::none);
boost::optional<int> op3(10);
int value = 10;
int value1 = 20;
boost::optional<int> op4(value);
boost::optional<int> op5(false, 20);
boost::optional<int> op6(true, 30);
boost::optional<int> op7(op4);
boost::optional<int> op8;
if (!op8) {
cout << "op8未初始化" << endl;
}
op8.emplace(10);
if (!op1) {
cout << "op1未初始化" << endl;
}
if (!op2) {
cout << "op2未初始化" << endl;
}
if (!op3) {
cout << "op3未初始化" << endl;
}
if (!op4) {
cout << "op4未初始化" << endl;
}
if (!op5) {
cout << "op5未初始化" << endl;
}
if (!op6) {
cout << "op6未初始化" << endl;
}
if (!op7) {
cout << "op7未初始化" << endl;
}
if (!op8) {
cout << "op8未初始化" << endl;
}
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
op8未初始化
op1未初始化
op2未初始化
op5未初始化
End
optional 采用了指针语义来访问内部保存的元素,这使得 optional 未初始化时的行为就像一个空指针,可以使用 operator bool() 和 operator!() 来检测是否有效。
optional 也重载了 operator* 和 operator-> 以实现与指针相同的操作,get() 和 get_ptr() 能够以函数的形式获得元素的引用和指针。需要注意的是它们内部仅使用 BOOST_ASSERT 提供基本的安全保证,如果未初始化,那么函数的行为是未定义的。
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/optional.hpp>
using namespace std;
class MyClass
{
public:
MyClass();
~MyClass();
int a = 10;
double d= 12.12;
private:
};
MyClass::MyClass()
{
}
MyClass::~MyClass()
{
}
int main()
{
cout << "Start" << endl;
boost::optional<int> op1;
MyClass myclass;
boost::optional<MyClass> op2(myclass);
boost::optional<int> op3(23);
cout << "get():" << op3.get() << endl;
cout << "*:" << *op3.get_ptr() << endl;
cout << *op3 << endl;
cout << op2.get_ptr()->a << endl;
cout << op2.get_ptr()->d << endl;
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
get():23
*:23
23
10
12.12
End
optional 另外提供了三个 value() 系列成员函数,它们更加安全。
-
value() 同样可以访问元素,如果 optional 未初始化,会抛出 bad_optional_access 异常。
-
value_or(default) 可以保证返回一个有效值,如果 optional 已初始化,那么返回内部的元素,否则返回 default。
-
value_or_eval(f) 类似 value_or(),但它的参数是一个可调用的函数或函数对象,如果 optional 未初始化,则返回 f 的执行结果,即 f()。
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/optional.hpp>
using namespace std;
int function1()
{
cout << "未定义" << endl;
return 99;
}
class MyClass
{
public:
MyClass();
~MyClass();
int value = 88;
int operator()()
{
return value;
}
private:
};
MyClass::MyClass()
{
}
MyClass::~MyClass()
{
}
int main()
{
cout << "Start" << endl;
boost::optional<int> op1;
boost::optional<int> op3(23);
//op1.value(); --未定义函数,获取值时会报错;
cout << "op2的value():" << op3.value() << endl;
cout << "op1的value_or(default):" << op1.value_or(11) << endl;
cout << "op2的value_or(default):" << op3.value_or(22) << endl;
cout << "op1的value_or_eval(f):" << op1.value_or_eval(function1) << endl;
MyClass myclass;
cout << "op1的value_or_eval(class):" << op1.value_or_eval(myclass) << endl;
cout << "op2的value_or_eval(f):" << op3.value_or_eval(function1) << endl;
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
op2的value():23
op1的value_or(default):11
op2的value_or(default):23
未定义
op1的value_or_eval(f):99
op1的value_or_eval(class):88
op2的value_or_eval(f):23
End
optional 全面支持比较运算,与普通指针的浅比较(仅比较指针值)不同,optional 进行的是深比较,同时,optional 加入了对未初始化情况的判断。
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/optional.hpp>
using namespace std;
int main()
{
cout << "Start" << endl;
boost::optional<int> op1;
boost::optional<int> op2(10);
boost::optional<int> op3(10);
boost::optional<int> op4(op3);
if (op1==op2) {
cout << "op1与op2相等" << endl;
}
else
{
cout << "op1与op2不相等" << endl;
}
if (op3 == op2) {
cout << "op3与op2相等" << endl;
}
else
{
cout << "op3与op2不相等" << endl;
}
if (op3 == op4) {
cout << "op3与op4相等" << endl;
}
else
{
cout << "op3与op4不相等" << endl;
}
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
op1与op2不相等
op3与op2相等
op3与op4相等
End
3. 用法
optional 的接口简单明了,把它看作一个大小为 1 且行为类似指针的容器就可以了,也可以把它想象成是一个类似 scoped_ptr、shared_ptr 的智能指针(但要注意,optional 不是智能指针,它与智能指针的用法类似但用途不同)。
示范 optional 基本用法的代码如下:
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/optional.hpp>
#include <vector>
using namespace std;
int main()
{
cout << "Start" << endl;
boost::optional<int> op0; //一个未初始化的 optional 对象
boost::optional<int> op1(boost::none); //同上,使用 none 赋予其未初始化值
assert(!op0); //bool 测试
assert(op0 == op1); //比较两个 optional 对象
assert(op1.value_or(253) == 253); //获取默认值
cout << op1.value_or_eval( //使用函数对象
[]() {return 874; }) << endl; //用 lambda 表达式定义函数对象
boost::optional<string> ops("test"); //初始化为字符串 test
cout << *ops << endl; //用解引用操作符获取值
ops.emplace("monado", 3); //就地创建一个字符串,没有拷贝代价
assert(*ops == "mon"); //只使用了前三个字符
vector<int> v(10);
boost::optional<vector<int>& > opv(v); //容纳一个容器的引用
assert(opv); //bool 转型
opv->push_back(5); //使用箭头操作符操纵容器
assert(opv->size() == 11);
opv = boost::none; //置为未初始化状态
assert(!opv); //此时为无效值
cout << "End" << endl;
system("pause");
return 0;
}
这段代码演示了 optional 的一些基本操作,接下来我们再看一个略微复杂的例子,下列代码使用 optional 作为函数的返回值,解决了本节一开始提出的几个问题:
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/optional.hpp>
#include <vector>
using namespace std;
boost::optional<double> calc(int x) //计算倒数
{
return boost::optional<double>(x != 0, 1.0 / x); //条件构造函数
}
boost::optional<double> sqrt_op(double x) //计算实数的平方根
{
return boost::optional<double>(x >= 0, sqrt(x)); //条件构造函数
}
int main()
{
cout << "Start" << endl;
boost::optional<double> d = calc(0);
if (d) //在 bool 语境下测试 optional 的有效性
{
cout << *d << endl;
}
else
{
cout << "未定义" << endl;
}
d = sqrt_op(-10);
if (!d) //使用重载的逻辑非操作符
{
cout << "no result" << endl;
}
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
未定义
no result
End
4. 工厂函数
optional 提供一个类似 make_pair()、make_shared() 的工厂函数 make_optional(),可以根据参数类型自动推导 optional 的类型,用来辅助创建 optional 对象。它的声明如下:
cpp
optional<T> make_optional(T const& v);
optional<T> make_optional(bool condition, T const& v);
但 make_optional() 无法推导出 T 引用类型的 optional 对象,如果需要一个 optional<T&> 的对象就不能使用 make_optional() 函数。
make_optional() 也不支持 emplace 的用法,可能存在值的拷贝代价。
示范 make_optional() 函数用法的代码如下:
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/optional.hpp>
#include <vector>
using namespace std;
int main()
{
cout << "Start" << endl;
auto x = boost::make_optional(5); //使用 auto 关键字自动推导类型
cout << *x << endl;
auto y = boost::make_optional<double>((*x > 0), 1.0); //使用模板参数明确类型
cout << *y << endl;
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
5
1
End