C++的optional用法实例详解

 更新时间:2024年02月27日 10:29:32   作者:leapmotion  
编程中我们可能会遇到要处理可能为空的变量,比如说容器,基本类型,或者说对象实例,下面通过实例代码介绍C++的optional用法,感兴趣的朋友一起看看吧

optional用法

1 问题引出

编程中我们可能会遇到要处理可能为空的变量,比如说容器,基本类型,或者说对象实例,我们简单看个例子:

#include <string>
#include <vector>
#include <memory>
struct Some
{
    int some_i_ = 0;
    std::string some_str_;
};
Some
getSome(const std::vector<Some>& 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<Some> someVec;
    someVec.push_back({1, "1"});
    Some s = getSome(someVec, 1);
    s = getSome(someVec, 2);
    return 0;
}

这里代码很简单,我们根据条件获取vector中一个元素,这个元素是个结构体,当满足条件时可以返回,但是没有找到时仍然要返回一个对象,到我们main函数甚至要花一些力气来判断有没有找到。如果没有找到在getSome返回空就好了,这样我们就来介绍optional

2 简介

使用std::optional能够达到上边的效果,我们简单了解下,首先optional是在c++17引入,可以看作是T类型和一个bool值的包装。
关于std::optional可以接受对象或者nullopt(表示为空值),参考一段例子:

#include <iostream>
#include <optional>
using namespace std;
int main()
{
  std::optional<int> pp = 1;
  if (pp) {
      cout << *pp << endl; // 1
  }
  pp = nullopt;
  if (pp) {
      cout << *pp << endl; // 不输出
  }
}

我们看这个简单的例子,pp用来存放int的对象,初始化为1,判断pp是否包含值,可以输出1,将nullopt赋值后,判断时为false,自然也不会输出。我们把上边遗留的那个例子重新写一下:

// snip...
#include <iostream>
using namespace std;
optional<Some> 
getSome(const std::vector<Some>& 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<Some> 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细则

创建optinal

有几种方式创建optional,我们具体看下例子:

直接创建或者用nullopt赋值

std::optional<int> empty;
std::optional<int> opt = std::nullopt; 

使用对象初始化

std::optional<int> opt = 1;
struct Some
{
  int some_i_ = 0;
  std::string some_str_;
};
Some s;
std::optional<Some> opt = s;

使用 std::make_optional构造,类似std::make_shared可以传递参数原地构造optional包含的对象

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<Some> opt = make_optional<Some>(1, "1");
auto opt = make_optional(1); // optional<int>

使用std::in_place构造:
其实使用std::in_place和使用std::make_optional 用法相近,都是原地构造对象,避免使用对象初始化进行的一次拷贝等。std::in_place只是一个tag,用来表示我们使用std::optional的那个构造函数。
optional的构造函数是这样:

//
template <class... _Args, class = enable_if_t<
      is_constructible_v<value_type, _Args...>>>
constexpr explicit 
optional(in_place_t, _Args&&... __args)
      : __base(in_place, _VSTD::forward<_Args>(__args)...) {}

//
template <class _Up, class... _Args, class = enable_if_t<
      is_constructible_v<value_type, initializer_list<_Up>&, _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<Some> opt {in_place, 1, "1"};

写起来要比std::make_optional简便很多

optional的其他操作

/// 1
optional<int> opt {1};
opt.value(); // 1
*opt // 1
/// 2
optional<int> opt;
opt.value(); // 抛出异常
*opt // 为定义
opt.value_or(2); // 2(没有值时使用默认值)
///3
optional<int> opt{2};
opt.emplace(4); // 重新构造4的对象
opt.reset(); // 释放掉原来的对象,nullopt

optional比较

和指针比较

大家是否在想指针是不是也可以达到这样的效果,我们来看一下:

  • 如果我们和普通的指针相比,即用指针指向对象,如果为空的时候使用nullptr来代替,对于我们第一个例子可以达到相似的效果,因为我们的vector的生命周期时在使用指针之后销毁,因为指针只是简单指向,对于指向已经析构的对象,无疑是一场灾难。
  • 如果和我们智能指针比较,例如第一个例子中,

第一种实现我们需要vector存放shared_ptr才能进行拷贝:

shared_ptr<Some> getSome(
    const vector<shared_ptr<Some>>& 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<Some> 
getSome(const vector<Some>& 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<Some>{&s};
    }
    return nullptr;
}

这样就和我们使用普通指针是一样的,并且shared_ptr引用计数为0的时候还是会做销毁,这样是错误的。
最后一种就是重新构造一个Some对象,普通指针和智能都可以实现。普通指针需要做delete操作,如果用智能指针实现也可以:

shared_ptr<Some>
getSome(const vector<Some>& 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<Some>(*iter);
    }
    return nullptr;
}

我们发现智能指针也可以充当这样的角色,如何使用要看大家了,不过既然推出了新的标准,而且如果要实现如此功能感觉还是optional使用起来方便一点,语义明确,而且代码可读性较好。

和rust的option比较

首先rust的option是一个枚举:

enum Option<T> {
  Some(T),
  None,
} 

这个枚举是个模版,枚举中每个元素可以存放对象或者不存放,类似之前例子的rust的简单实现:

fn getSome(b: bool) -> Option<i32> {
    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

到此这篇关于C++的optional解析的文章就介绍到这了,更多相关C++ optional内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++实现视频流转换为图片方式

    C++实现视频流转换为图片方式

    今天小编就为大家分享一篇C++实现视频流转换为图片方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • c++快速排序详解

    c++快速排序详解

    快速排序总体思想:先找到一个枢轴,让他作为分水岭,通过一趟排序将待排序的记录分割成两部分,前面一部分都比枢轴小,后面一部分枢轴大,然后又分别对这两部分记录继续进行递归的排序,达到整个序列有序的目的
    2017-05-05
  • C\C++ 获取当前路径实例详解

    C\C++ 获取当前路径实例详解

    这篇文章主要介绍了C\C++ 获取当前路径实例详解的相关资料,需要的朋友可以参考下
    2017-06-06
  • VS2019创建C++工程的的实现步骤

    VS2019创建C++工程的的实现步骤

    本文主要介绍了VS2019创建C++工程步骤,包含新建项目、编辑文件、配置源文件目录、编译链接、输出文件、设置断点调试,具有一定的参考价值,感兴趣的可以了解一下
    2024-12-12
  • C语言详细讲解strcpy strcat strcmp函数的模拟实现

    C语言详细讲解strcpy strcat strcmp函数的模拟实现

    这篇文章主要介绍了怎样用C语言模拟实现strcpy与strcat和strcmp函数,strcpy()函数是C语言中的一个复制字符串的库函数,strcat()函数的功能是实现字符串的拼接,strcmp()函数作用是比较字符串str1和str2是否相同
    2022-05-05
  • C++实现评教管理系统

    C++实现评教管理系统

    这篇文章主要为大家详细介绍了C++实现评教管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • Visual Studio Code上添加小程序自动补全插件的操作方法

    Visual Studio Code上添加小程序自动补全插件的操作方法

    这篇文章主要介绍了Visual Studio Code上添加小程序自动补全插件的操作方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • c语言调用汇编的方法

    c语言调用汇编的方法

    在此记录一下c调用汇编的方法,汇编使用的是AT&T语法。例子很简单,就是在给一个整数用汇编转换成二进制
    2013-11-11
  • C语言水仙花数的实现

    C语言水仙花数的实现

    这篇文章主要介绍了C语言水仙花数的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • 使用C语言绘制统计图中的饼图

    使用C语言绘制统计图中的饼图

    常用的统计图有条形图、柱形图、折线图、曲线图、饼图、环形图、扇形图,本文主要为大家详细介绍了如何使用使用C语言绘制统计图中的饼图,希望对大家有所帮助
    2024-02-02

最新评论