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 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数,由于篇幅过大,分为两篇讲解,下面开始下篇
    2022-04-04
  • C语言实现可排序通讯录的示例代码

    C语言实现可排序通讯录的示例代码

    本文主要介绍了C语言实现可排序通讯录的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • VC++实现程序开机启动运行的方法

    VC++实现程序开机启动运行的方法

    这篇文章主要介绍了VC++实现程序开机启动运行的方法,很实用的功能,需要的朋友可以参考下
    2014-08-08
  • C++函数指针+对象指针+this指针+指向类静态和非静态成员的指针

    C++函数指针+对象指针+this指针+指向类静态和非静态成员的指针

    这篇文章主要介绍了C++函数指针+对象指针+this指针+指向类静态和非静态成员的指针,函数指针定义和赋值的语法指其中数据类型代表指向函数的返回类型,形参表为指向函数的形参表,更多相关资料需要的朋友可以参考一下下面文章内容
    2022-03-03
  • C++连连看判定图形消除算法

    C++连连看判定图形消除算法

    这篇文章主要为大家详细介绍了C++连连看判定图形消除算法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • 数据结构之带头结点的单链表

    数据结构之带头结点的单链表

    单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:数据域(数据元素的映象) + 指针域(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据
    2023-07-07
  • C语言实现堆的简单操作的示例代码

    C语言实现堆的简单操作的示例代码

    堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。本文介绍了C语言中堆的一些简单操作,需要的可以参考一下
    2022-11-11
  • VC编程控件类HTControl之CHTGDIManager GDI资源管理类用法解析

    VC编程控件类HTControl之CHTGDIManager GDI资源管理类用法解析

    这篇文章主要介绍了VC编程控件类HTControl之CHTGDIManager GDI资源管理类用法解析,需要的朋友可以参考下
    2014-08-08
  • C++游戏教程基本技巧之随机化详解

    C++游戏教程基本技巧之随机化详解

    在小游戏的制作中时常常会要用到随机数,这篇文章就来和大家谈谈C++中这个所谓的“随机”。文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-11-11
  • C++ spdlog 日志库使用小结

    C++ spdlog 日志库使用小结

    spdlog是目前 C++ 生态中性能最高(每秒可处理数百万条消息)、使用最广泛的现代日志库之一,下面就来详细的介绍下spdlog 日志库使用,感兴趣的可以了解一下
    2026-06-06

最新评论