仿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上测试结果如下:

 参考:

Boost源码剖析之:泛型指针类any之海纳百川

 C++中的类型擦除(type erasure in c++)

(原创)c++中的类型擦除

(原创)用c++11打造好用的any

 

posted @ 2018-11-14 14:22  逆向人  阅读(1094)  评论(0)    收藏  举报