仿boost::any的泛型指针类any的实现
在boost库中,any是一种特殊容器,只能容纳一个元素,但这个元素可以是任意的类型----int、double、string、标准容器或者任何自定义类型。程序可以用any保存任意的数据,也可以在任何需要的时候取出any中的数据。any类目前已经加入到c++17标准中,在vs2017中include<any>头文件即可使用。
vs2017里的标准库any的头文件最后有句提示:
#pragma message("class any is only available with C++17 or later.") 也就是说此any类无法在支持C++17标准之前的编译器中使用。
仿boost的any实现如下,代码加了注释,方便读者学习理解
1 //any类设计要点 2 //1.any类不能是一个模板类形如int i;any<int>anyValue=i;无意义,还不如直接写int anyValue =i; 3 //2.any必须提供模板构造函数(赋值操作不必是模板的),才能完成如下操作: 4 // int i; long j; struct X; X x;any anyValue(i); anyValue=j; anyValue =x; 5 //3.必须提供某些有关它所保存的对象型别的信息。 6 //4.它必须能保存数值且提供某种方法将它保存的数值“取出来”。 7 //5.数据不能放在any类里,这会使any类成为模板类,不符合1的要求。 8 // 数据应该动态存放,即动态分配一个数据的容器来存放数据,而any类中则保存指向这个容器的指针。 9 // 明确地说,是指向这个容器的基类的指针,这是因为容器本身必须为模板,而any类中的指针成员又必须不是泛型的。 10 // 因为any不能是泛型的,所以any中所有数据成员都不能是泛型的。 11 // 所以,结论是:为容器准备一个非泛型的基类,而让指针指向该基类。 12 #pragma once 13 #include <memory> 14 #include <typeindex> 15 #include <exception> 16 #include <iostream> 17 struct Any 18 { 19 Any(void) : m_tpIndex(std::type_index(typeid(void))) { 20 std::cout << "Any(void) function called!" << std::endl; } 21 Any(const Any& that) : m_ptr(that.Clone()), m_tpIndex(that.m_tpIndex) { 22 std::cout << "Any(const Any& that) called!" << std::endl; } 23 Any(Any && that) : m_ptr(std::move(that.m_ptr)), m_tpIndex(that.m_tpIndex) { 24 std::cout << " Any(Any && that) function called!" << std::endl; } 25 //创建智能指针时,对于一般的类型,通过std::decay来移除引用和cv符,从而获取原始类型 26 //cv符即const和violate 27 //类似int i;long j;anyValue(i),anyValue(j)时调用模板构造函数 28 //注意构造函数中的new Derived。由于Derived是模板类,U里包含的类型信息可以完整的被保留。 29 //构造函数本身又可以传进任意类型,这样就实现了类型类型擦除 30 template<typename U, class = typename std::enable_if<!std::is_same<typename std::decay<U>::type, Any>::value, U>::type> 31 Any(U && value) : m_ptr(new Derived < typename std::decay<U>::type>(std::forward<U>(value))),\ 32 m_tpIndex(std::type_index(typeid(typename std::decay<U>::type))){ 33 std::cout << "Any(U && value) template function called!" << std::endl;} 34 template<class U> bool Is() const 35 { 36 std::cout << "Any::Is() template function called!" << std::endl; 37 return m_tpIndex == std::type_index(typeid(U)); 38 } 39 bool IsNull() const { 40 std::cout << "Any::IsNull() function called!" << std::endl; 41 return !bool(m_ptr); 42 } 43 //AnyCast方法的思想是期望程序员们清楚自己在做什么,要不然就给他个异常瞧瞧。 44 45 //AnyCast模仿了boost提供的any_cast<>模板方法,在这里是Any的成员函数 46 //取出原始类型,需要客户提供类型信息 如 anyValue.AnyCast<int> 47 //m_tpIndex里保存了关于原始数据的型别信息,不存在boost::any实现时丢失型别信息的情况 48 //所以boost单独提供了any_cast版本出来,而这里不需要 49 template<class U> 50 U& AnyCast() 51 { 52 std::cout << "Any::AnyCast() template function called!" << std::endl; 53 //判断类型信息是否匹配,不匹配报错 54 if (!Is<U>()) 55 { 56 std::cout << "can not cast " << typeid(U).name() << " to " << m_tpIndex.name() << std::endl; 57 throw std::logic_error{ "bad cast" }; 58 } 59 //匹配了,则取出原始数据 60 auto derived = dynamic_cast<Derived<U>*> (m_ptr.get()); 61 return derived->m_value; 62 } 63 //赋值操作重载不是模板函数,不存在模板无穷递归即循环赋值问题 64 Any& operator=(const Any& a) 65 { 66 std::cout << "Any& operator= function called!" << std::endl; 67 //防止自赋值 68 if (m_ptr == a.m_ptr) 69 return *this; 70 //两个数据成员赋值 71 m_ptr = a.Clone(); 72 m_tpIndex = a.m_tpIndex; 73 return *this; 74 } 75 private: 76 struct Base; 77 typedef std::unique_ptr<Base> BasePtr; 78 //泛型数据容器Derived的非泛型基类 79 struct Base 80 { 81 virtual ~Base() { 82 std::cout << " virtual ~Base() function called!" << std::endl; 83 } //虚析构函数,为保证派生类对象能用基类指针析构 84 virtual BasePtr Clone() const = 0; //复制容器 85 }; 86 87 //Derived是个模板类,各个类成员之间可以共享类模板参数的信息 88 //所以,可以方便地用原数据类型来进行各种操作 89 template<typename T> 90 struct Derived : Base 91 { 92 template<typename U> 93 Derived(U && value) : m_value(std::forward<U>(value)) { 94 std::cout << "Derived(U && value) function called!" << std::endl; 95 } 96 BasePtr Clone() const 97 { 98 std::cout << "Derived::Clone() function called!" << std::endl; 99 return BasePtr(new Derived<T>(m_value)); //改写虚函数,返回自身的复制体,动态构造 100 } 101 T m_value; //原始数据保存的地方 102 }; 103 //any类转发Clone操作给BasePtr的Clone方法,实现数据复制 104 BasePtr Clone() const 105 { 106 std::cout << "Any::Clone() function called!" << std::endl; 107 if (m_ptr != nullptr) 108 return m_ptr->Clone(); 109 return nullptr; 110 } 111 //通过基类指针擦除具体类型 112 //any类赋值时需要创建派生类对象,这里用std::unique_ptr管理派生类对象生命周期 113 BasePtr m_ptr; 114 std::type_index m_tpIndex; //提供关于型别的信息 115 };
测试代码如下:
1 // testany.cpp : Defines the entry point for the console application. 2 // 3 #include "stdafx.h" 4 #include "any.hpp" 5 #include <string> 6 int _tmain(int argc, _TCHAR* argv[]) 7 { 8 Any n; 9 std::cout << n.IsNull() << std::endl; 10 std::string str = "Hello"; 11 //赋值操作,其实分两步进行 12 //先调用any的移动构造函数把str转换为any类型 13 //再调用any的赋值构造函数完成真正的赋值 14 n = str; 15 try 16 { 17 std::cout << n.AnyCast<int>() << '\n'; 18 } 19 catch (std::exception& e) 20 { 21 std::cout << "Exception: " << e.what() << '\n'; 22 } 23 std::cout << std::endl; 24 std::cout << "-----------------------------" << std::endl; 25 //直接初始化只调用移动构造函数,比采用赋值构造少了很多步骤,所以直接初始化效率更高 26 Any n1(1); 27 std::cout << n1.Is<int>() << '\n'; 28 std::cout << std::endl; 29 std::cout << "-----------------------------" << std::endl; 30 Any n2(std::move(n1)); 31 std::cout << n2.AnyCast<int>() << '\n'; 32 std::cout << std::endl; 33 system("pause"); 34 return 0; 35 }
在vs2013上测试结果如下:

参考:
C++中的类型擦除(type erasure in c++)
|
作者:逆向人 公众号:逆向人 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。 |

浙公网安备 33010602011771号