目录

std::variant(c17)

questions and existing problems

variant: and VS or

Why not Union

Solution

variant in boost

variant in stdlib

item1 get && get_if

item2 std::visit

item3 duplicate entries

item4 no special recursion

item5 std::monostate

item6 allocation at assignment removed

overload lambda for visit

other substitution

std:optional(c17)

std:any(C17)

item1 different storage strategy

item2 copy while  any_cast


std::variant(c17)

在2004年boost 1.31.0库引入了variant,但是直到16年std::variant正式被引入标准库c++17

在看cpp reference的时候,我深恶痛绝的一点就是没有一个从浅到深的例子来说明应用场景,这也是我写博客的原因。

所以我们为什么需要variant?

questions and existing problems

假设一个场景,我们有一个类,类里面存储了两种信息分别是name,和index,但是这两种信息类型不同在任何时候有且只有一种信息存在,因此我们有一个bool 类的has name来区分所含信息的种类。

同时为了涵盖所有的情况,我们不得不写出两个接口分别是getname和getindex。

int root.h file

#pragma once
#include<iostream>
#include <string>
using namespace std;class PersonId
{
public://behavior is undefined unless hasName()==truestring getname();//behavior is undefined unless hasName()==falseint getIndex();bool hasName() {return this->has_name;}
private:bool has_name;string name;int index;
};

这样就会导致三个问题:

  1. 当用户使用代码的时候没办法确保满足情况
  2. 浪费空间,因为反正只会有一种类型存在
  3. 样板代码(Boilerplate Code),因为我们实际上只会调用一个接口

第二个example

say,我们有这样一个例子,我们根据数据流做一些操作,首先我们定义一个接口,然后我们指定一某一个操作为set_value。并且特化其中的put函数。

struct command
{virtual ostream& put(ostream&) = 0;virtual ~command() {};
};
struct set_score :public command
{ostream& put(ostream&) override final {};double value;
};

同样也有一些问题:

  1. 因为引入了多态和继承,我们需要用智能指针管理对象
  2. 样板代码(Boilerplate Code),因为众多代码是重复的
  3. 基类的提供让用户用了自定义衍生类的机会,这不是我们想要的
  4. 函数是分散的,也就意味着如果我们想添加一个新的函数,需要更改base class

一种解决方法如下:

struct command
{enum class type {SET_SCORE,FIRE_MISSLE,FIRE_LASER,ROTATE};virtual type getType() = 0;virtual ~command() {};
};
struct set_score :public command
{type getType() override final { return type::SET_SCORE; };double value;
};

这样写的好处显而易见:

  1. 首先函数不再分散了,我们可以通过getType找到对应的派生类,我们可以直接dynamic_cast(safe downcasting)到对应的子类然后调用函数,不用更改基类的接口
  2. 但是还需要智能指针管理内存
  3. 样板代码(Boilerplate Code),同样的问题
  4. 拼写错误,如果return了错误,只有在run-time才能检测出来

variant: and VS or

那么什么是variant?

我们可以简单这样认为:

struct S
{X x,Y y,
};

上面的代码代表S包含x和y两个type的两个变量

variant<X, Y>S;

代表S包含XorY type;

Why not Union

有人会说Union不是也可以多个不同的type共享一块空间吗?实际上union本身就有一些缺陷:

  1. 对象不知道它们当前持有的值的类型
  2. 对象必须是non-trival的,比如string
  3. 不能派生union

Solution

对于第一个example PersonID,我们可以这样写:

using personID = variant<int, string>;

and that is it.

对于第二个example command ,我们可以这样用variant替代:

struct set_score
{double value;
};
using command=variant<set_score,fire_missile,fire_laser,rotate>

《》内部是我们具体的实现类

variant in boost

因为std引入了variant,因此boost种的意义不大,就简单说一下,

  1. 初始化的时候会自动初始化为<>种的第一个类,因此typeid.name()输出是其中的类而不是variant,这在std种也是如此,只是type不同。
  2. 访问其中的元素会用到类似c++14 get的用法get<T>(variant),访问特定类型的元素
  3. 也可以用operator()重载结构体visitor的方式来定义匹配对应的元素
struct visitor
{
public:void operator()(const string& s)const {cout << "I got string" << endl;}void operator()(const int s)const{cout << "I got int" << endl;}
};

variant in stdlib

item1 get && get_if

保留了get<T>,并且多了一个get_if<T&>,如果不是就返回空, 否则返回指针

using PersonID = variant<string, int>;
int main()
{PersonID P;P = "ss";if (string const* const s = get_if<string>(&P)) {string ss = *s;cout << ss << endl;}else {cout << get<int>(P) << endl;}
}

output:

ss

我们也可以用idex来代替type,get_if和get用法不变

item2 std::visit

保留了visit,但是还是需要用户自己写visitor函数:

struct visitor
{
public:void operator()(const string& s)const{cout << "I got string" << endl;}void operator()(const int s)const{cout << "I got int" << endl;}
};
int main()
{PersonID P;P = "ss";visit(visitor(),P);
}

item3 duplicate entries

同一个variant可以有同样的type,比如这样:

using PersonID = variant<string, string>;

很多人发现这样有问题,没办法初始化,其实方法简单,用emplace(c14)就可以:

 std::variant<std::string, std::string> w;w.emplace<1>(string("dads"));cout << w.index() << endl;

这样就可以区分是扔进去的第一个还是第二个了……当然实际开发中应该很少这么写(sad)

item4 no special recursion

也就是说没办法写递归,比如我们定义二叉树:

template<typename T>
struct binaryTree;template<typename T>
struct binaryBranch
{shared_ptr<binaryTree<T>> left;shared_ptr<binaryTree<T>> right;
};
template<typename T>
struct binaryTree
{using value = variant<T, binaryBranch<T>>;value V;
};
int main()
{binaryTree<int> B = 1;//error
}

item5 std::monostate

boost库种原本为boost::blank,std种为monostate,熟悉英语的应该能猜到这叫单一状态 ,本身它也只起一个占位的作用,为无法初始化的variant提供一个可以初始化的机会;

struct A
{A(int i) {};
};
using testType = variant<A, int>;
int main()
{testType t;//error
}

我们可以这样写:

struct A
{A(int i) {};
};
using testType = variant<monostate,A, int>;
int main()
{testType t;//OK
}

item6 allocation at assignment removed

类模板std::variant代表一个类型安全的union。安全类型可以简单理解为不会扔出异常的,但是如果扔出了,会放置在额外的空间中,否则不会也不允许开辟额外空间。我们可以用valueless_by_exception()的返回值来检查是否为空。

但是搞笑的是,既然已经扔出异常了,我们通常情况下程序会直接矶钓,根本不会有机会让我们去这样检查,所以基本上这个东西用不到。乐

overload lambda for visit

下面展示一种酷酷的visitor的写法:

template<class... Ts> struct overload : Ts... {overload(Ts...) = delete;using Ts::operator()...;
};using testType = variant<string, int>;
void myVisitor(testType& t)
{return std::visit(overload{[](const int i) { cout << "int" << endl; },[](const std::string& s) {cout << "string" << endl; }}, t);
}

其实visitor底部就是index:

template <typename F, typename... Variants>
decltype(auto) visit(F f, Variants... vs) {return f(std::get<vs.index()>(vs)...);
}

还是推荐大多多用index啊。

other substitution

std:optional(c17)

在实际开发中,我们会n次地去判断内容是否为空,optional提供了一种更优雅的方式来判断内容。

optional<string> openfile(const string& path)
{ifstream stream(path);if (stream) {string res((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());stream.close();return res;}return {};
}
int main()
{optional<string>data = openfile("data.txt").value();if (data.has_value()) {cout << data.value() << endl;}
}

std:any(C17)

any不需要我们指定template,类似于variant。

 any data;data = 1;data = "string";string str = any_cast<string>(data);//we got a string type

尽管这样更方便,但是也意味着更不安全,而且会有性能损失,因此还是建议大家使用variant

item1 different storage strategy

还有个更重要的区别就是存储方式,variant意味着本身是个safe的union,但是any不是这样存储的,any 的source code种有这样一部分:

struct _Storage_t {union {unsigned char _TrivialData[_Any_trivial_space_size];_Small_storage_t _SmallStorage;_Big_storage_t _BigStorage;};uintptr_t _TypeData;};

看一下big和samll storage就能发现对于大型的struct,存储方式为void*而不是union,是通过动态分配的方式给出的。

    struct _Small_storage_t {unsigned char _Data[_Any_small_space_size];const _Any_small_RTTI* _RTTI;};static_assert(sizeof(_Small_storage_t) == _Any_trivial_space_size);struct _Big_storage_t {// Pad so that _Ptr and _RTTI might share _TypeData's cache lineunsigned char _Padding[_Any_small_space_size - sizeof(void*)];void* _Ptr;const _Any_big_RTTI* _RTTI;};

如果我们gose deeper,就会发现 small storage的大小大概为32byte,也就是说大于32byte的会用动态分配的策列。

constexpr int _Small_object_num_ptrs = 6 + 16 / sizeof(void*);
inline constexpr size_t _Any_small_space_size = (_Small_object_num_ptrs - 2) * sizeof(void*);

item2 copy while  any_cast

这也就意味着,我们在any_cast的时候永远都是获取的右值,是一个新的值,这在variant的get种是可以实现的。

string& str = any_cast<string>(data);//error

如果我们仍然希望得到引用,那么这样写回提升一点点性能:

 string& str = any_cast<string&>(data);//we got a lreference to string type

c++ advanced(8) std::variant from beginner to expert相关推荐

  1. C++17类型std::variant介绍

    std::variant代表了类型安全的union类型,与union类似,std::variant的一个实例要么存储了可选类型中的一个值,要么没有存储.但与union比,其优势是可以判断当前真实的存储 ...

  2. C++ std::any、std::variant和std::optional的原位构造(In-Place Construction)

    本文翻译自 Bartlomiej Filipek 的博客文章 In-Place Construction for std::any, std::variant and std::optional,翻译 ...

  3. std::variant 与 std::visit

    std::variant 简介 std::variant 是c++17 引入的一个类型,其作用类似于C语言中的Union,但是比Union 的功能强大的多. C语言中一个联合体Union 可以储存多种 ...

  4. C++17之std::variant

    从C中采用的c++提供了对union的支持,union是能够保存可能类型列表之一的对象.但是,这种语言特性也有一些缺点: 对象不知道它们当前持有的值的类型. 由于这个原因,您不能有non-trivia ...

  5. c++17 std::variant

    c++17 std::variant 说明 代码 输出 参考 说明 类模板 std::variant表示一个类型安全的联合体. std::variant 的一个实例在任意时刻要么保有其一个可选类型之一 ...

  6. 访问boost::variant std::variant

    std::visit 定义于头文件 <variant>     template <class Visitor, class... Variants> constexpr /* ...

  7. 关于C++ variant 类型问题

    一直想知道C++标准库有没有类似Qt下QVariant功能的类.整理了下网上关于这部分的说法: 1. std::variant 的实现是 C++17的, 使用需要考虑编译器的支持 2. _varian ...

  8. C++17之std::visit

    它们必须明确地为每种可能的类型提供函数调用操作符.然后,使用相应的重载来处理当前的备选项类型. 1. 使用对象函数方式访问 例1: #include <iostream> #include ...

  9. std::expected以及其开源实现

    std::expected以及其开源实现 背景 std::expected和std::optional的区别 std::expected的接口介绍 基本接口 Monad接口 std::expected ...

最新文章

  1. android launcher
  2. 【论文笔记】Sparse filtering
  3. 你真的会玩SQL吗?EXISTS和IN之间的区别
  4. Python爬虫(十一)_案例:使用XPath的爬虫
  5. 怎样在IDEA上将WebService接口打包部署到服务器
  6. 流程流转相关业务与流转的分离
  7. 她说程序员不懂浪漫,生日宴上惨变单身狗,其实,程序员的浪漫你不懂!
  8. Android之使用ViewPager实现图片展示(最简单的)
  9. HTML5 Canvas中绘制线段
  10. html5 人脸,HTML5 可拖拉的人*皮(人脸)面具
  11. 动态规划之多重部分和问题
  12. Spring Boot 中的线程池,这也太好用了!
  13. 目前web渗透的思路
  14. Python输入一个三位数,输出其个位数字、十位数字和百位数字。
  15. u盘带走的绿化wamp配置方式
  16. tumblr_使用Google Analytics(分析)获取有关您的Tumblr博客的详细统计信息
  17. itext7读取pdf 中文_itext7史上最全实战总结
  18. acc之ADTS解说一
  19. ios-app杀死状态下响应推送
  20. .net Core WebApi记录

热门文章

  1. 狼和兔子PHP,狼和兔子
  2. Xendit获得1.5亿美元C轮融资,用于在东南亚构建数字支付基础设施
  3. 基于matlab的航迹发生器模拟,仿真输出经纬度、高度、俯仰、航向角等
  4. IPv6路由FIB通知链
  5. dy创作者平台上传并发布视频
  6. 易语言运行cmd命令move移动文件
  7. 在阿里云镜像站下载blender
  8. MPS MP2307DN-LF-Z 单片同步降压调节器
  9. 软件测试,如何月薪过万?
  10. 如何将pdf转换成图片,这三个方法简单又方便!