目录
在日常编程中,我们可能会遇到这么一个场景:需要一个类型可以接收任意类型的变量,并且在需要使用该变量的时候还能恰当的进行转换。不难想到,C语言中的万能指针void*可以满足我们上述的需求。但void*的使用相对繁琐,且难免会涉及到大量的内存管理操作,这无疑加大了我们编程的复杂度。而在C++17中,any类的出现很好的解决了我们上述的问题。
一、any类简介
std::any
是 C++17 引入的一个标准库类型,用于表示一个可以存储任意类型数据的容器。与 std::variant
不同,std::any
不限制存储的类型,因此它可以用来存储任意的对象。它的设计目标是提供一种简单的方式来存储和检索任意类型的值,而不需要像 void*
那样手动管理类型信息。
1. std::any
的基本特性
- 任意类型的存储:
std::any
可以存储任何可拷贝构造的类型。 - 类型安全:
std::any
提供了类型安全的访问,确保在访问值时不会发生类型错误。 - 动态类型:
std::any
可以在运行时存储不同类型的对象,而无需在编译时指定类型。
2. std::any
的成员函数
2.1 构造函数
std::any()
:默认构造函数,创建一个空的std::any
对象。std::any(const std::any& other)
:拷贝构造函数,创建一个与other
相同内容的std::any
对象。std::any(std::any&& other) noexcept
:移动构造函数,创建一个从other
移动内容的std::any
对象。template<typename T> std::any(T&& value)
:从任意类型T
构造std::any
对象。
#include <any>
#include <iostream>
int main() {
std::any a = 42; // 存储一个 int
std::any b = "hello"; // 存储一个 const char*
std::any c = std::string("world"); // 存储一个 std::string
}
2.2 赋值操作符
std::any& operator=(const std::any& other)
:拷贝赋值操作符。std::any& operator=(std::any&& other) noexcept
:移动赋值操作符。template<typename T> std::any& operator=(T&& value)
:从任意类型T
赋值。
#include <any>
#include <iostream>
int main() {
std::any a;
a = 42; // 存储一个 int
a = "hello"; // 存储一个 const char*
a = std::string("world"); // 存储一个 std::string
}
2.3 has_value
bool has_value() const noexcept
:检查std::any
是否包含一个值。
#include <any>
#include <iostream>
int main() {
std::any a;
if (!a.has_value()) {
std::cout << "a is empty\n";
}
a = 42;
if (a.has_value()) {
std::cout << "a has a value\n";
}
}
2.4 type
const std::type_info& type() const noexcept
:返回当前存储值的类型信息。
#include <any>
#include <iostream>
int main() {
std::any a = 42;
if (a.type() == typeid(int)) {
std::cout << "a contains an int\n";
}
}
3. std::any_cast
std::any_cast
是用于从 std::any
对象中安全地提取值的工具。它提供了类型安全的访问方式,如果类型不匹配,则抛出 std::bad_any_cast
异常。
3.1 std::any_cast
的基本用法
template<typename T> T any_cast(std::any& operand)
:提取std::any
中的值,返回T
类型的引用。template<typename T> T any_cast(const std::any& operand)
:提取std::any
中的值,返回T
类型的常量引用。template<typename T> T any_cast(std::any&& operand)
:提取std::any
中的值,返回T
类型的右值引用。template<typename T> const T* any_cast(const std::any* operand)
:提取std::any
中的值,返回T
类型的指针。template<typename T> T* any_cast(std::any* operand)
:提取std::any
中的值,返回T
类型的指针。
#include <any>
#include <iostream>
int main() {
std::any a = 42;
int value = std::any_cast<int>(a);
std::cout << "Value: " << value << '\n';
// 如果类型不匹配,会抛出异常
try {
std::string str = std::any_cast<std::string>(a);
} catch (const std::bad_any_cast& e) {
std::cout << "Caught exception: " << e.what() << '\n';
}
}
3.2 std::any_cast
的安全检查
std::any_cast
也可以返回一个指针,这样可以避免抛出异常,并且可以在没有值的情况下返回 nullptr
。
#include <any>
#include <iostream>
int main() {
std::any a = 42;
if (int* value = std::any_cast<int>(&a)) {
std::cout << "Value: " << *value << '\n';
} else {
std::cout << "a does not contain an int\n";
}
}
4. std::any
的应用场景
- 通用数据存储:
std::any
可以用于存储任意类型的数据,适合在需要存储不同类型数据的场景中使用。 - 动态类型处理:在运行时需要处理不同类型的数据时,
std::any
可以作为一种通用的解决方案。
二、模拟实现any类
any类的设计思想
⾸先Any类肯定不能是⼀个模板类,否则编译的时候 Any<int> a, Any<float>b,需要传类型作为模板参数,也就是说在使⽤的时候就要确定其类型。显然,我们希望的是直接定义一个Any对象能够接收不同类型的对象。如:Any a = std::string("hello"); 或 Any a = 10;。
我们如何在不进行显式实例化的同时还能接收各类型的对象呢?我们知道,想要接收任意类型的对象,就务必要使用模板,但模板参数的实际类型是在编译时才进行确认的,但我们又无法直接对Any类使用模板,因此Any类的成员变量也一定不是模板,而是一个既定类型的变量。因此考虑Any内部设计⼀个模板容器subholder类,该类可以保存各种类型数据。
而因为在Any类中⽆法定义这个subholder对象或指针,因为any也不知道这个类要保存什么类型的数据,因此⽆法传递类型参数。所以,定义⼀个基类placehoder,让subholder继承于placeholder,⽽Any类保存⽗类指针即可。
当需要保存数据时,由于模板可以自行推导所传入参数的类型,因此我们在Any类构造函数或赋值运算符重载函数的设计中可以使用模板函数,然后在函数内部new⼀个带有模板参数的子类subholder对象出来保存数据,然后让Any类中的⽗类指针指向这个子类对象就搞定了。例如:
template <typename T>
Any(const T &operand)
: _content(new subholder<T>(operand))
{}
这其实是使用了多态的思想:假设Any类中有一个非模板成员变量content,要使得该变量可以接收任意类型的值,我们可以使用“多态的思想”。在C++多态中,基类对象可以接收子类对象——会进行“切片处理”保证赋值的正确性,且在需要的时候,可以对基类对象进行强制转换使其转换为子类对象。
设计框架
-
Any
类的设计目标:- 提供一个通用的容器,能够存储任意类型的数据(类似于
std::any
)。 - 支持在运行时获取存储数据的类型信息(通过
typeid
)。 - 支持深拷贝、赋值操作,以及类型安全的值提取(
any_cast
)。
- 提供一个通用的容器,能够存储任意类型的数据(类似于
-
核心设计思想:
- 使用多态和模板技术实现类型擦除(Type Erasure)。
- 通过派生类(
subholder<T>
)存储具体类型的数据,并通过基类指针(placeholder*
)进行管理。
-
类层次结构:
placeholder
类:- 是一个纯虚基类,定义了接口(
type()
和clone()
),不能直接实例化。 - 所有具体类型的数据存储类(
subholder<T>
)都继承自它。
- 是一个纯虚基类,定义了接口(
subholder<T>
类:- 继承自
placeholder
,用于存储具体类型的数据(T _val
)。 - 实现了
type()
和clone()
接口,提供类型信息和深拷贝功能。
- 继承自
class Any
{
// 基类
class placeholder // 包含纯虚函数,是抽象类,不能直接实例化对象
{
public:
virtual ~placeholder() {
/*placeholder 类是一个纯虚类,
它的析构函数需要提供一个实现,否则会导致链接错误。
纯虚析构函数的实现通常是一个空函数体。*/
}
virtual const std::type_info &type() = 0; // 返回参数的类型
virtual placeholder *clone() = 0; // 克隆出一个相同的对象,实现深拷贝
};
template <typename T>
class subholder : public placeholder // 子类
{
public:
subholder(const T &operand) : _val(operand) {}
~subholder() {} // 析构
const std::type_info &type() override // 返回对象的数据类型
{
return typeid(T);
}
placeholder *clone() override
{
return new subholder(_val);
}
public:
T _val; // 数据对象
};
private:
placeholder *_content; // 定义基类指针
public:
Any() {};
template <typename T>
Any(const T &operand);
bool has_value(); // 是否已经有值
Any(const Any &operand); //拷贝构造
template <typename T>
Any &operator=(const T &operand); // 赋值运算符重载
Any &operator=(Any operand); // 赋值运算符重载
void swap(Any &operand); // 交换数据
const std::type_info &type(); // 返回保存的对象的数据类型
template <typename T>
T *get_val(); // 获取指向数据对象的指针
};
完整代码
#include <iostream>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <cassert>
namespace MyAny
{
class Any
{
class placeholder // 包含纯虚函数,是抽象类,不能直接实例化对象
{
public:
virtual ~placeholder() {
/*placeholder 类是一个纯虚类,
它的析构函数需要提供一个实现,否则会导致链接错误。
纯虚析构函数的实现通常是一个空函数体。*/
}
virtual const std::type_info &type() = 0; // 返回参数的类型
virtual placeholder *clone() = 0; // 克隆出一个相同的对象,实现深拷贝
};
template <typename T>
class subholder : public placeholder // 子类
{
public:
subholder(const T &operand) : _val(operand) {}
~subholder() {} // 析构
const std::type_info &type() override // 返回对象的数据类型
{
return typeid(T);
}
placeholder *clone() override
{
return new subholder(_val);
}
public:
T _val; // 数据对象
};
private:
placeholder *_content; // 定义基类指针
public:
Any() : _content(nullptr) {};
template <typename T>
Any(const T &operand) : _content(new subholder<T>(operand)){};
bool has_value() // 是否已经有值
{
return _content != nullptr;
}
Any(const Any &operand)
{
_content = operand._content == nullptr ? nullptr : operand._content->clone();
}
template <typename T>
Any &operator=(const T &operand)
{
/*为operand构建⼀个临时对象出来,然后进⾏交换,这样临时对象销毁的时候,顺带原先
保存的placeholder也会被销毁*/
Any(operand).swap(*this);
return *this;
}
Any &operator=(Any operand) // 构造一个新的临时对象,直接交换资源即可
{
// if(has_value()) {delete _content; _content = nullptr;} 使用拷贝交换惯用法,已经将资源托管给临时对象,无需这一步
swap(operand);
return *this;
}
void swap(Any &operand)
{
std::swap(_content, operand._content);
}
const std::type_info &type()
{
return _content->type();
}
template <typename T>
T *get_val()
{
if (typeid(T) != type())
assert(nullptr);
return &(((subholder<T> *)_content)->_val);
}
~Any()
{
if(has_value()) delete _content;
_content = nullptr;
}
};
template <typename T>
T any_cast(Any &operand)
{
if (typeid(T) != operand.type())
throw("type mismatch");
return *(operand.get_val<T>());
}
template <typename T>
T *any_cast(Any *operand)
{
if (typeid(T) != operand->type())
throw("type mismatch");
return operand->get_val<T>();
}
}
测试用例
class Test
{
public:
std::string _data;
public:
Test(const std::string &data) : _data(data) { std::cout << "构造" << _data << std::endl; }
Test(const Test &other)
{
_data = other._data;
std::cout << "拷⻉" << _data << std::endl;
}
~Test() { std::cout << "析构" << _data << std::endl; }
};
using namespace MyAny;
int main()
{
// 测试用例1
Any a;
a = std::string("asdasdasd");
std::string ret = any_cast<std::string>(a);
std::cout << ret << std::endl;
Any b = 10;
std::cout << any_cast<int>(b) << std::endl;
b = a;
std::cout << *b.get_val<std::string>() << std::endl;
std::cout << any_cast<std::string>(a) << std::endl;
// // 测试用例2
// Any a;
// {
// Test t("1");
// a = t;
// }
// while(1);
return 0;
}
标签:std,const,C++,Any,operand,type,any,模拟
From: https://blog.csdn.net/2301_76606232/article/details/145032271