IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++ advanced(8) std::variant from beginner to expert -> 正文阅读

[C++知识库]c++ advanced(8) std::variant from beginner to expert

目录

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()==true
	string getname();
	//behavior is undefined unless hasName()==false
	int 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 line
        unsigned 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++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 17:22:53  更:2022-04-18 17:26:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/14 14:27:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码