给网站做,seo伪原创工具,新乡网站seo,造价师在哪个网站做继续教育文章目录 一、类型擦除的作用二、常见的类型擦除容器1.std::any2.std::function3.std::shared_ptr\void\和 std::unique_ptr\void\4.总结 三、实现一个any参考 类型擦除#xff08;Type Erasure#xff09;是一种编程技术#xff0c;通过它可以在运行时存储… 文章目录 一、类型擦除的作用二、常见的类型擦除容器1.std::any2.std::function3.std::shared_ptr\void\和 std::unique_ptr\void\4.总结 三、实现一个any参考 类型擦除Type Erasure是一种编程技术通过它可以在运行时存储和操作不同类型的对象同时隐藏这些类型的具体信息。C 标准库中提供了几种常见的类型擦除容器用于存储和操作多态对象。这些容器包括但不限于以下几种:
std::anyC17 引入std::functionstd::shared_ptr 和 std::unique_ptrboost::anyBoost 库boost::functionBoost 库
一、类型擦除的作用
类型擦除就是将原有类型消除或者隐藏。
为什么要擦除类型因为很多时候我不关心具体类型是什么或者根本就不需要这个类型通过类型擦除我们可以获取很多好处比如使得我们的程序有更好的扩展性、还能消除耦合以及消除一些重复行为使程序更加简洁高效。
归纳一下c中类型擦除方式主要有如下五种
第一种通过多态来擦除类型第二种通过模板来擦除类型第三种通过某种容器来擦除类型第四种通过某种通用类型来擦除类型第五种通过闭包来擦除类型
第一种类型隐藏的方式最简单也是我们经常用的通过将派生类型隐式转换成基类型再通过基类去多态的调用行为在这种情况下我不用关心派生类的具体类型我只需要以一种统一的方式去做不同的事情所以就把派生类型转成基类型隐藏起来这样不仅仅可以多态调用还使我们的程序具有良好的可扩展性。然而这种方式的类型擦除仅仅是部分的类型擦除因为基类型仍然存在而且这种类型擦除的方式还必须是继承方式的才可以而且继承使得两个对象强烈的耦合在一起了正是因为这些缺点通过多态来擦除类型的方式有较多局限性效果也不好。
时我们通过第二种方式擦除类型以解决第一种方式的一些缺点。通过模板来擦除类型本质上是把不同类型的共同行为进行了抽象这时不同类型彼此之间不需要通过继承这种强耦合的方式去获得共同的行为了仅仅是通过模板就能获取共同行为降低了不同类型之间的耦合是一种很好的类型擦除方式。然而第二种方式虽然降低了对象间的耦合但是还有一个问题没解决就是基本类型始终需要指定并没有消除基本类型例如我不可能把一个T本身作为容器元素必须在容器初始化时就要知名这个T是具体某个类型。
需要注意的是第四和第五种方式虽然解决了第三种方式不能彻底消除基本类型的缺点但是还存一个缺点就是取值的时候仍然依赖于具体类型无论我是通过get还是any_cast我都要T的具体类型这在某种情况下仍然有局限性。
eg我有A、B、C、D四种结构体每个结构体中有某种类型的指针名称且称为info我现在提供了返回这些结构体的四个接口供外接使用有可能是c#或者dephi调用这些接口由于结构体中的info指针是我分配的内存所以我必须提供释放这些指针的接口。代码如下
struct A
{int* info;int id;
};struct B
{double* info;int id;
};struct C
{char* info;int id;
};struct D
{float* info;int id;
};//对外提供的删除接口
void DeleteA(A t)
{delete t.info;
}void DeleteB(B t)
{delete t.info;
}void DeleteC(C t)
{delete t.info;
}void DeleteD(D t)
{delete t.info;
}大家可以看到增加的四个删除函数内部都是重复代码本来通过模板函数一行搞定但是没办法c#可没有c的模板还得老老实实的提供这些重复行为的接口而且这种方式还有个坏处就是每增加一种类型就得增加一个重复的删除接口怎么办能统一成一个删除接口吗可以一个可行的办法就是将分配的内存通过一个ID关联并保存起来让外接传一个ID告诉我要删那块内存新的统一删除函数可能是这样
//内部将分配的内存存到map中让外面传ID内部通过ID去删除对应的内存块
mapint, T mapT;templatetypename R, typename T
R GetT()
{R result{1,new T()};mapT.insert(std::pairint, T(1, R)); return result;
}//通过ID去关联我分配的内存块外面传ID内部通过ID去删除关联的内存块
void DeleteT(const int id)
{R t mapT[id]-second();delete t.info;
}很遗憾上面的代码编译不过因为mapint, T mapT只能保存一种类型的对象无法把分配的不同类型的对象保存起来我们可以通过方式三和方式四用variant或者any去擦除类型解决T不能代表多种类型的问题第一个问题解决。但是还有第二个问题DeleteT时从map中返回的variant或者any无法取出来因为接口函数中没有类型信息而取值方法get和any_cast都需要一个具体类型。似乎进入了死胡同无法只提供一个删除接口了。但是办法总还是有的。
方式五隆重登场了看似无解的问题通过方式五就能解决了。通过闭包来擦除类型很好很强大。
闭包也可以称为匿名函数或者lamda表达式c11中的lamda表达式就是c中的闭包c11引入lamda实际上引入了函数式编程的概念函数式编程有很多优点使代码更简洁而且声明式的编码方式更贴近人的思维方式。函数式编程在更高的层次上对不同类型的公共行为进行了抽象从而使我们不必去关心具体类型。
std::map int, std::function void() m_freeMap; //保存返回出去的内存块templatetypename R, typename T
R GetResult()
{R result GetTableR, T(); m_freeMap.insert(std::make_pair(result.sequenceId, [this, result]{FreeResult(result); }));
}bool FreeResultById(int memId){auto it m_freeMap.find(memId);if (it m_freeMap.end())return false;it-second(); //delete by lamdam_freeMap.erase(memId);return true;}通过闭包去擦除类型可以解决前面四种擦除方式遇到的问题
二、常见的类型擦除容器
1.std::any
std::any 是 C17 引入的一种类型擦除容器可以存储任何类型的值并在运行时决定具体类型。
#include any
#include iostream
#include stringint main() {std::any a 42;std::cout std::any_castint(a) std::endl;a std::string(Hello, World!);std::cout std::any_caststd::string(a) std::endl;return 0;
}
2.std::function
std::function 是一种通用的多态函数封装器可以存储和调用任何可调用对象包括函数指针、lambda 表达式、绑定表达式或其他函数对象。
#include functional
#include iostreamvoid foo() {std::cout foo std::endl;
}int main() {std::functionvoid() f foo;f();f []() { std::cout lambda std::endl; };f();return 0;
}
3.std::shared_ptrvoid和 std::unique_ptrvoid
智能指针 std::shared_ptr 和 std::unique_ptr 可以通过指向 void 类型来实现类型擦除从而存储任意类型的对象。
#include iostream
#include memoryint main() {std::shared_ptrvoid p std::make_sharedint(42);std::cout *std::static_pointer_castint(p) std::endl;p std::make_sharedstd::string(Hello, World!);std::cout *std::static_pointer_caststd::string(p) std::endl;return 0;
}
4.总结
这些类型擦除容器在需要存储和操作多态对象时非常有用可以在运行时决定具体类型而不需要在编译时知道类型的具体信息。根据具体需求和项目依赖可以选择使用标准库或 Boost 库提供的类型擦除容器。
三、实现一个any
any的设计思路Any内部维护了一个基类指针通过基类指针擦除具体类型any_cast时再通过向下转型获取实际数据。当转型失败时打印详情。
any能容纳所有类型的数据因此当赋值给any时需要将值的类型擦除才行即以一种通用的方式保存所有类型的数据。这里可以通过继承去擦除类型基类是不含模板参数的派生类中才有模板参数这个模板参数类型正是赋值的类型在赋值时将创建的派生类对象赋值给基类指针基类的派生类中携带了数据类型基类只是原始数据的一个占位符通过多态它擦除了原始数据类型因此任何数据类型都可以赋值给他从而实现了能存放所有类型数据的目标。
当取数据时需要向下转换成派生类型来获取原始数据当转换失败时打印详情并抛出异常。
由于any赋值时需要创建一个派生类对象所以还需要管理该对象的生命周期这里用unique_ptr智能指针去管理对象的生命周期。
#include iostream
#include string
#include memory
#include typeindex
struct Any
{Any(void) : m_tpIndex(std::type_index(typeid(void))){}Any(const Any that) : m_ptr(that.Clone()), m_tpIndex(that.m_tpIndex) {}Any(Any that) : m_ptr(std::move(that.m_ptr)), m_tpIndex(that.m_tpIndex) {}//创建智能指针时对于一般的类型通过std::decay来移除引用和cv符从而获取原始类型templatetypename U, class typename std::enable_if!std::is_sametypename std::decayU::type, Any::value, U::type Any(U value) : m_ptr(new Derived typename std::decayU::type(forwardU(value))),m_tpIndex(type_index(typeid(typename std::decayU::type))){}bool IsNull() const { return !bool(m_ptr); }templateclass U bool Is() const{return m_tpIndex type_index(typeid(U));}//将Any转换为实际的类型templateclass UU AnyCast(){if (!IsU()){cout can not cast typeid(U).name() to m_tpIndex.name() endl;throw bad_cast();}auto derived dynamic_castDerivedU* (m_ptr.get());return derived-m_value;}Any operator(const Any a){if (m_ptr a.m_ptr)return *this;m_ptr a.Clone();m_tpIndex a.m_tpIndex;return *this;}private:struct Base;typedef std::unique_ptrBase BasePtr;struct Base{virtual ~Base() {}virtual BasePtr Clone() const 0;};templatetypename Tstruct Derived : Base{templatetypename UDerived(U value) : m_value(forwardU(value)) { }BasePtr Clone() const{return BasePtr(new DerivedT(m_value));}T m_value;};BasePtr Clone() const{if (m_ptr ! nullptr)return m_ptr-Clone();return nullptr;}BasePtr m_ptr;std::type_index m_tpIndex;
};测试
void TestAny()
{Any n; auto r n.IsNull();//truestring s1 hello;n s1;n world;n.AnyCastint(); //can not cast int to stringAny n1 1;n1.Isint(); //true
}其他
templatetypename T
T any_cast(any aAny)
{if (typeid(T) aAny.type_){return *static_castT*(aAny.data_);}else{throw std::bad_any_cast{};}
}参考
原创c中的类型擦除How std::any Works