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 int
与int
得到不同的结果。这一点很重要,因为如果类型不匹配,后面获取数据时就会抛出异常!
拷贝构造的困难和解决方案
在写拷贝构造(上面代码的第三个函数)时,我们遇到了问题。由于是深拷贝,我们肯定不能直接复制指针,而是应该再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)
{