C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ std::any

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

作者:[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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:
阅读全文