4.3 Boost 库工具类 optional 的使用

背景

在实际的软件开发过程中我们经常会遇到无效值的情况。例如,函数并不是总能返回有效值,很多时候即使函数正确执行,但其结果却不是合理的值。如果用数学语言来解释,这种情况就是返回值位于函数解空间之外。

求一个数的倒数、在实数域内开平方、在字符串中查找子串,它们都可能返回无效值。有些无效返回的情况下可以用抛出异常的方式来通知用户,但在某些情况下,这样代价很高或不允许抛出异常,这时必须要以某种合理的、高效的方式通知用户。

表示无效值最常用的做法是增加一个"哨兵"的角色,它位于解空间之外,如 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
相关推荐
_OP_CHEN2 小时前
算法基础篇:(五)基础算法之差分——以“空间”换“时间”
c++·算法·acm·icpc·算法竞赛·差分算法·差分与前缀和
秋风&萧瑟2 小时前
【C++】智能指针介绍
java·c++·算法
有梦想的攻城狮2 小时前
我与C++的一面之缘
开发语言·c++
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于java的民宿管理小程序为例,包含答辩的问题和答案
java·开发语言·小程序
ᐇ9592 小时前
Java集合框架:深入理解List与Set及其实现类
java·开发语言
啟明起鸣2 小时前
【Go 与云原生】先从 Go 对与云原生的依赖关系讲起,再讲讲 一个简单的 Go 项目热热身
开发语言·云原生·golang
oioihoii2 小时前
《C语言点滴》——笑着入门,扎实成长
c语言·开发语言
waves浪游3 小时前
基础开发工具(下)
linux·运维·服务器·开发语言·c++
QX_hao3 小时前
【Go】--log模块的使用
开发语言·后端·golang