C++ std::any的模拟实现

 更新时间:2024年02月03日 15:00:55   作者:[PE]经典八炮  
std::any是C++标准库中的一个类,std::any对象可以存储除单例等特殊情况外的任何类型的数据,本文主要介绍了C++ std::any的模拟实现,具有一定的参考价值,感兴趣的可以了解一下

std::any

std::any是C++标准库中的一个类,官网对它的描述如下:

类 any 描述用于任何可拷贝构造类型的单个值的类型安全容器。

类 any 的对象存储任何满足构造函数要求的类型的一个实例或为空,而这被称为 any 类对象的状态。存储的实例被称作所含对象。若两个状态均为空,或均为非空且其所含对象等价,则两个状态等价。

非成员 any_cast 函数提供对所含对象的类型安全访问。

换句话说,std::any对象可以存储任何类型的数据(单例等特殊情况除外)。这篇文章来探讨一下如何自己实现一个Any类。

Any的基本原理

在C++这种强类型语言中,想用一种类型来保存多种类型的数据,首先想到的就是用父类指针(或引用)来保存子类,实现运行时多态。但问题是,我们想要保存任意类型,必须使所有类型都有一个公共的父类。在某些语言(如Java)中,有一个Object类,是所有类的父类,因此这种语言中就非常容易实现。但C++的类型系统相当混乱,原生类型没有父类,STL的类型也没有一个公共父类,而自定义类型也不会自动继承自一个公共父类,因此直接用父类指针不可行。但是如果我们把模板和继承结合一下就可以了,为每一种类型创建一个对应的模板类,这个模板类又继承自一个父类。核心代码如下:

class AnyHelperBase
{
};
template<typename T>
class AnyHelper :public AnyHelperBase
{
	T data;
};

这样我们就可以用AnyHelperBase*类型来存储任意类型的数据了。当然,这只是大体思路,还需要具体完善。下面我们将以上述代码为母体,添加功能。

将数据存储到Any

Any类

在上面的代码中,如何将数据存储到Any?肯定需要一个AnyHelperBase*的类型。但考虑到直接操作指针不是很方便,并且std::any使用的时候并不需要指针,我们应该再写一个类来维护AnyHelperBase*

class Any
{
private:
	class AnyHelperBase
	{
	public:
	};
	template<typename T>
	class AnyHelper :public AnyHelperBase
	{
	public:
		T data;
	};
	AnyHelperBase* data;
public:
	
};

构造函数

接下来实现AnyHelper的构造函数(第一个是就地构造,直接通过参数构造data,后两个是拷贝构造):

template<typename ...Args>
AnyHelper(Args&&... args) :data(std::forward<Args>(args)...) {}
AnyHelper(const AnyHelper& other) :data(other.data) {}
AnyHelper(const T& value) :data(value) {}

Any类的构造函数:

Any() :data(nullptr) {}
template<typename T>
Any(const T& value) : data(new AnyHelper<std::decay_t<T>>(value)) {}
//Any(const Any& other) :data( ??? ) {}
Any(Any&& other) :data(other.data)
{
	other.data = nullptr;
}

注意:std::decay_t<T>的作用是去掉T的const,引用等乱七八糟的属性,比如std::decay_t<const int&>的结果是int。例如,我们显然不希望传入const intint得到不同的结果。这一点很重要,因为如果类型不匹配,后面获取数据时就会抛出异常!

拷贝构造的困难和解决方案

在写拷贝构造(上面代码的第三个函数)时,我们遇到了问题。由于是深拷贝,我们肯定不能直接复制指针,而是应该再new一个对象。但问题是,我们怎么获取另一个Any中的类型呢?这个问题似乎不好解决,因为只有在AnyHelper类内部我们才会知道存储的类型(这句话很重要)。但我们可以变通一下,让AnyHelper类直接返回一个自身的拷贝的指针,我们不必关心他具体是什么类型。当然,我们使用的是AnyHelperBase*,所以AnyHelperBase类里必须就得有这个函数,换句话说,这得是一个虚函数。在这里我们又用到了多态的特性。往AnyHelperBase和AnyHelper中添加Clone函数:

class AnyHelperBase
{
public:
	virtual AnyHelperBase* Clone()const = 0;
};
template<typename T>
class AnyHelper :public AnyHelperBase
{
public:
	T data;
	template<typename ...Args>
	AnyHelper(Args&&... args) :data(std::forward<Args>(args)...) {}
	AnyHelper(const AnyHelper& other) :data(other.data) {}
	AnyHelper(const T& value) :data(value) {}
	virtual AnyHelper* Clone()const
	{
		return new AnyHelper(*this);
	}
};

Any类的拷贝构造函数:

Any(const Any& other) :data(other.data->Clone()) {}

赋值运算符

赋值运算符和构造函数基本一样,需要注意的是delete原来的data

template<typename T>
Any& operator=(const T& value)
{
	if (data != nullptr)
		delete data;
	data = new AnyHelper<std::decay_t<T>>(value);
	return *this;
}
Any& operator=(const Any& other)
{
	if (data != nullptr)
		delete data;
	data = other.data->Clone();
	return *this;
}
Any& operator=(Any&& other)
{
	if (data != nullptr)
		delete data;
	data = other.data;
	other.data = nullptr;
	return *this;
}

其他赋值类函数

注意到std::any可以有空值,并且可以设置为空,我们也写一个Reset函数将Any设为空。

void Reset()
{
	if (data != nullptr)
		delete data;
	data = nullptr;
}

另外,为了优化性能,并且支持一些不可移动和拷贝的类型,我们添加就地构造函数,可以直接通过参数构造一个对象。

template<typename T, typename ...Args>
std::decay_t<T>& Emplace(Args&&... args)
{
	if (data != nullptr)
		delete data;
	auto temp = new AnyHelper<std::decay_t<T>>(std::forward<Args>(args)...);
	data = temp;
	return temp->data;
}

还有一个简单的Swap,直接交换data指针:

void Swap(Any& other)
{
	AnyHelperBase* temp = this->data;
	this->data = other.data;
	other.data = temp;
}

到这里,Any类就可以存储数据了。

从Any获取数据:Any转换为其他类型

对一个实用的Any类来说,获取数据也是必不可少的,实现获取数据即将Any转换为其他类型。对std::any来说,有std::any_cast函数来实现这一转换,我们也写一个AnyCast函数。

template<typename T>
T AnyCast(const Any& any)
{
	auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data);
	if (p == nullptr)
		throw std::runtime_error("Bad any cast!");
	return p->data;
}
template<typename T>
T AnyCast(Any& any)
{
	auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data);
	if (p == nullptr)
		throw std::runtime_error("Bad any cast!");
	return p->data;
}
template<typename T>
T AnyCast(Any&& any)
{
	auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data);
	if (p == nullptr)
		throw std::runtime_error("Bad any cast!");
	return p->data;
}
template<typename T>
const T* AnyCast(const Any* any)
{
	auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any->data);
	if (p == nullptr)
		return nullptr;
	return &p->data;
}
template<typename T>
T* AnyCast(Any* any)
{
	auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any->data);
	if (p == nullptr)
		return nullptr;
	return &p->data;
}

AnyCast一共有5个重载(和STL中的一致),前三个是一组,后两个是一组。前三个的特性是转换失败会抛出异常,后两个接受指针,返回指针,失败不会抛异常,而是会返回空指针。5个函数实现原理都是一样的,核心就是使用dynamic_cast将AnyHelperBase*类型的data转换为相应的AnyHelper子类。dynamic_cast是向下转换的操作符,也支持运行时多态,如果转换失败会返回空指针。

获取Any信息

到现在,一个Any类的核心功能已经全部完成,不过为了模拟std::any,我们还是再添加一些获取信息的函数。

获取类型的type_info

我们前面说过,在我们实现的Any类中,只有在AnyHelper类内部我们才会知道存储的类型。因此,获取类型必须从AnyHelper类下首。类似于Clone,我们再为AnyHelperBase和AnyHelper添加一个虚函数:

class AnyHelperBase
{
public:
	virtual const std::type_info& Type()const = 0;
	virtual AnyHelperBase* Clone()const = 0;
};
template<typename T>
class AnyHelper :public AnyHelperBase
{
public:
	T data;
	//构造函数省略
	//...
	virtual const std::type_info& Type()const
	{
		return typeid(T);
	}
	virtual AnyHelper* Clone()const
	{
		return new AnyHelper(*this);
	}
};

这样Any类的Type就好写了:

const std::type_info& Type()const
{
	return data->Type();
}

HasValue

没啥好说的…

bool HasValue()const
{
	return data != nullptr;
}

析构函数

在这里我提醒一下大家,虽然析构函数很简单,但一定不要忘了写,否则会引起内存泄漏!检查析构函数是一个很好的代码习惯!

~Any()
{
	if (data != nullptr)
		delete data;
}

附录:完整代码

到这里,整个Any类就完成了。下面是完整代码:

namespace MyStd
{
	class Any
	{
	private:
		class AnyHelperBase
		{
		public:
			virtual const std::type_info& Type()const = 0;
			virtual AnyHelperBase* Clone()const = 0;
		};
		template<typename T>
		class AnyHelper :public AnyHelperBase
		{
		public:
			T data;
			template<typename ...Args>
			AnyHelper(Args&&... args) :data(std::forward<Args>(args)...) {}
			AnyHelper(const AnyHelper& other) :data(other.data) {}
			AnyHelper(const T& value) :data(value) {}
			virtual const std::type_info& Type()const
			{
				return typeid(T);
			}
			virtual AnyHelper* Clone()const
			{
				return new AnyHelper(*this);
			}
		};
		template<typename T>
		friend T AnyCast(const Any& any);
		template<typename T>
		friend T AnyCast(Any& any);
		template<typename T>
		friend T AnyCast(Any&& any);
		template<typename T>
		friend const T* AnyCast(const Any* any);
		template<typename T>
		friend T* AnyCast(Any* any);
		AnyHelperBase* data;
	public:
		Any() :data(nullptr) {}
		template<typename T>
		Any(const T& value) : data(new AnyHelper<std::decay_t<T>>(value)) {}
		Any(const Any& other) :data(other.data->Clone()) {}
		Any(Any&& other) :data(other.data)
		{
			other.data = nullptr;
		}
		const std::type_info& Type()const
		{
			return data->Type();
		}
		bool HasValue()const
		{
			return data != nullptr;
		}
		void Reset()
		{
			if (data != nullptr)
				delete data;
			data = nullptr;
		}
		template<typename T>
		Any& operator=(const T& value)
		{
			if (data != nullptr)
				delete data;
			data = new AnyHelper<std::decay_t<T>>(value);
			return *this;
		}
		Any& operator=(const Any& other)
		{
			if (data != nullptr)
				delete data;
			data = other.data->Clone();
			return *this;
		}
		Any& operator=(Any&& other)
		{
			if (data != nullptr)
				delete data;
			data = other.data;
			other.data = nullptr;
			return *this;
		}
		void Swap(Any& other)
		{
			AnyHelperBase* temp = this->data;
			this->data = other.data;
			other.data = temp;
		}
		template<typename T, typename ...Args>
		std::decay_t<T>& Emplace(Args&&... args)
		{
			if (data != nullptr)
				delete data;
			auto temp = new AnyHelper<std::decay_t<T>>(std::forward<Args>(args)...);
			data = temp;
			return temp->data;
		}
		~Any()
		{
			if (data != nullptr)
				delete data;
		}
	};

	template<typename T>
	T AnyCast(const Any& any)
	{
		auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data);
		if (p == nullptr)
			throw std::runtime_error("Bad any cast!");
		return p->data;
	}
	template<typename T>
	T AnyCast(Any& any)
	{
		auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data);
		if (p == nullptr)
			throw std::runtime_error("Bad any cast!");
		return p->data;
	}
	template<typename T>
	T AnyCast(Any&& any)
	{
		auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data);
		if (p == nullptr)
			throw std::runtime_error("Bad any cast!");
		return p->data;
	}
	template<typename T>
	const T* AnyCast(const Any* any)
	{
		auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any->data);
		if (p == nullptr)
			return nullptr;
		return &p->data;
	}
	template<typename T>
	T* AnyCast(Any* any)
	{
		auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any->data);
		if (p == nullptr)
			return nullptr;
		return &p->data;
	}
}

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

相关文章

  • C++浅析虚函数使用方法

    C++浅析虚函数使用方法

    对C++了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。本文就将详细讲讲虚函数表的原理与使用,需要的可以参考一下
    2022-08-08
  • C语言植物大战数据结构二叉树堆

    C语言植物大战数据结构二叉树堆

    这篇文章主要为大家介绍了C语言植物大战数据结构二叉树堆的图文示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • C++11 std::transform函数使用小结

    C++11 std::transform函数使用小结

    std::transform是C++标准库中的一个算法,它用于对输入范围内的元素进行操作,并将结果存储在输出范围内,本文就介绍了std::transform函数的具体使用,感兴趣的可以了解一下
    2023-09-09
  • 浅理解C++ 人脸识别系统的实现

    浅理解C++ 人脸识别系统的实现

    这篇文章主要介绍了浅理解C++ 人脸识别系统的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • C++ Queue队列类模版实例详解

    C++ Queue队列类模版实例详解

    这篇文章主要为大家详细介绍C++ Queue队列类模版实例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • C语言超详细讲解队列的实现及代码

    C语言超详细讲解队列的实现及代码

    队列(Queue)与栈一样,是一种线性存储结构,它具有如下特点:队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构。在队尾添加元素,在队头删除元素
    2022-04-04
  • C++ 基类指针和子类指针相互赋值的实现方法

    C++ 基类指针和子类指针相互赋值的实现方法

    下面小编就为大家带来一篇C++ 基类指针和子类指针相互赋值的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-12-12
  • C语言之详解静态变量static

    C语言之详解静态变量static

    在C语言中static是用来修饰变量和函数的,这篇文章详细介绍了static主要作用,文章中有详细的代码实例,需要的朋友可以参考阅读
    2023-04-04
  • C语言详细讲解const的用法

    C语言详细讲解const的用法

    今天探讨const,首先来说是将变量常量化。为什么要将变量常量化,原因有诸多好处有诸多。比如可以使数据更加安全不会被修改
    2022-05-05
  • C++ IO设备读写功能实现详解

    C++ IO设备读写功能实现详解

    C++的文件IO(Input,Output)操作就是指对文件进行读写(输入与输出)的操作。输入就是从磁盘上的文件中读取内容到内存中。输出就是将内存中的数据内容输出或者说写入到磁盘的文件中,这篇文章主要介绍了C++ IO设备读写功能实现
    2022-11-11

最新评论