编程中我们可能会遇到要处理可能为空的变量,比如说容器,基本类型,或者说对象实例,我们简单看个例子:
#include
#include
#include
struct Some
{
int some_i_ = 0;
std::string some_str_;
};
Some
getSome(const std::vector& svec,
int i)
{
auto iter =
std::find_if(svec.begin(), svec.end(),
[i](const Some& s) {
return s.some_i_ == i;
}
);
if (iter != svec.end()) {
return *iter;
}
return Some();
}
int main()
{
std::vector someVec;
someVec.push_back({1, "1"});
Some s = getSome(someVec, 1);
s = getSome(someVec, 2);
return 0;
}
这里代码很简单,我们根据条件获取vector中一个元素,这个元素是个结构体,当满足条件时可以返回,但是没有找到时仍然要返回一个对象,到我们main函数甚至要花一些力气来判断有没有找到。如果没有找到在getSome返回空就好了,这样我们就来介绍optional
使用std::optional能够达到上边的效果,我们简单了解下,首先optional是在c++17引入,可以看作是T类型和一个bool值的包装。
关于std::optional可以接受对象或者nullopt(表示为空值),参考一段例子:
#include
#include
using namespace std;
int main()
{
std::optional pp = 1;
if (pp) {
cout << *pp << endl; // 1
}
pp = nullopt;
if (pp) {
cout << *pp << endl; // 不输出
}
}
我们看这个简单的例子,pp用来存放int的对象,初始化为1,判断pp是否包含值,可以输出1,将nullopt赋值后,判断时为false,自然也不会输出。我们把上边遗留的那个例子重新写一下:
// snip...
#include
using namespace std;
optional
getSome(const std::vector& svec, int i)
{
auto iter = std::find_if(svec.begin(), svec.end(), [i](const Some& s) {
return s.some_i_ == i;
});
if (iter != svec.end()) {
return *iter;
}
return nullopt;
}
int main()
{
vector someVec;
someVec.push_back({1, "11"});
auto s_ptr = getSome(someVec, 1);
if (s_ptr) {
cout << s_ptr->some_str_ << endl; // “11”
}
s_ptr = getSome(someVec, 2);
if (s_ptr) {
cout << s_ptr->some_str_ << endl; // 不输出
}
return 0;
}
我们把getSome的返回值的类型改为用optional包装,如果满足条件用Some对象填充,没有时用nullopt填充,在main函数里判断使用即可。
有几种方式创建optional,我们具体看下例子:
std::optional empty;
std::optional opt = std::nullopt;
std::optional opt = 1;
struct Some
{
int some_i_ = 0;
std::string some_str_;
};
Some s;
std::optional opt = s;
struct Some
{
Some(int i, std::string str):
some_i_(i),
some_str_(std::move(str)) {}
int some_i_ = 0;
std::string some_str_;
};
using namespace std;
optional opt = make_optional(1, "1");
auto opt = make_optional(1); // optional
//
template >>
constexpr explicit
optional(in_place_t, _Args&&... __args)
: __base(in_place, _VSTD::forward<_Args>(__args)...) {}
//
template &, _Args...>>>
constexpr explicit
optional(in_place_t, initializer_list<_Up> __il, _Args&&... __args)
: __base(in_place, __il, _VSTD::forward<_Args>(__args)...) {}
这里两个构造函数参数都是以in_place_t类型为第一个参数,就是表示一个占位符,后边我们传入要构造对象的参数。我们参考例子:
struct Some
{
Some(int i, std::string str):
some_i_(i),
some_str_(std::move(str)) {}
int some_i_ = 0;
std::string some_str_;
};
using namespace std;
optional opt {in_place, 1, "1"};
写起来要比std::make_optional简便很多
/// 1
optional opt {1};
opt.value(); // 1
*opt // 1
/// 2
optional opt;
opt.value(); // 抛出异常
*opt // 为定义
opt.value_or(2); // 2(没有值时使用默认值)
///3
optional opt{2};
opt.emplace(4); // 重新构造4的对象
opt.reset(); // 释放掉原来的对象,nullopt
大家是否在想指针是不是也可以达到这样的效果,我们来看一下:
shared_ptr getSome(
const vector>& svec,
int i)
{
auto iter = std::find_if(svec.begin(), svec.end(), [i](const Some& s) {
return s.some_i_ == i;
});
if (iter != svec.end()) {
return *iter;
}
return nullptr;
}
实现起来有点繁琐,并且还需要改动svec,这不妥。或者看起来这样:
shared_ptr
getSome(const vector& svec, int i)
{
auto iter = std::find_if(svec.begin(), svec.end(), [i](const Some& s) {
return s.some_i_ == i;
});
if (iter != svec.end()) {
Some s = *iter;
return shared_ptr{&s};
}
return nullptr;
}
这样就和我们使用普通指针是一样的,并且shared_ptr引用计数为0的时候还是会做销毁,这样是错误的。
最后一种就是重新构造一个Some对象,普通指针和智能都可以实现。普通指针需要做delete操作,如果用智能指针实现也可以:
shared_ptr
getSome(const vector& svec, int i)
{
auto iter = std::find_if(svec.begin(), svec.end(),
[i](const Some& s) {
return s.some_i_ == i;
});
if (iter != svec.end()) {
return std::make_shared(*iter);
}
return nullptr;
}
我们发现智能指针也可以充当这样的角色,如何使用要看大家了,不过既然推出了新的标准,而且如果要实现如此功能感觉还是optional使用起来方便一点,语义明确,而且代码可读性较好。
首先rust的option是一个枚举:
enum Option {
Some(T),
None,
}
这个枚举是个模版,枚举中每个元素可以存放对象或者不存放,类似之前例子的rust的简单实现:
fn getSome(b: bool) -> Option {
if b {
return Some(3);
}
return None;
}
fn main() {
let b = false;
if let Some(s) = getSome(b) {
println!("hello.. {}", s);
}
else {
println!("hello.. null");
}
}
getSome如果满足条件返回Some,不满足返回None。
rust致力于一个安全的语言,option是prelude,不需要显示引入作用域,同样不需要Option::前缀来直接使用Some和None,同时还配套和一些相关安全的函数,看起来比C++的简便一些,我们这里就做一个对比。
https://en.cppreference.com/w/cpp/utility/optional/optional
https://kaisery.gitbooks.io/trpl-zh-cn/content/ch06-01-defining-an-enum.html
等