Type erasure techniques
(对于类型擦除,我的意思是隐藏一些或所有关于类的类型信息,有点像boost.any。)我想掌握类型擦除技术,同时也分享我所知道的那些技术。我希望能找到一些疯狂的技巧,有人在他/她的最黑暗的时刻想到。:)
我知道,第一个也是最明显也是最常用的方法是虚拟函数。只需将类的实现隐藏在基于接口的类层次结构中。许多boost库都会这样做,例如boost.any这样做是为了隐藏您的类型和boost.shared&ptr这样做是为了隐藏(de)分配机制。
然后有一个选项,其中包含指向模板化函数的函数指针,同时将实际对象保存在一个
所以,对于我的实际问题:你还知道什么其他类型的擦除技术?如果可能的话,请向他们提供示例代码、用例、您对它们的体验,或者提供进一步阅读的链接。
编辑(因为我不确定是把这个作为答案,还是编辑这个问题,所以我会做一个更安全的。)另一个隐藏没有虚拟功能或
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | #include <iostream> #include <string> // NOTE: The class name indicates the underlying type erasure technique // this behaves like the Boost.Any type w.r.t. implementation details class Any_Virtual{ struct holder_base{ virtual ~holder_base(){} virtual holder_base* clone() const = 0; }; template<class T> struct holder : holder_base{ holder() : held_() {} holder(T const& t) : held_(t) {} virtual ~holder(){ } virtual holder_base* clone() const { return new holder<T>(*this); } T held_; }; public: Any_Virtual() : storage_(0) {} Any_Virtual(Any_Virtual const& other) : storage_(other.storage_->clone()) {} template<class T> Any_Virtual(T const& t) : storage_(new holder<T>(t)) {} ~Any_Virtual(){ Clear(); } Any_Virtual& operator=(Any_Virtual const& other){ Clear(); storage_ = other.storage_->clone(); return *this; } template<class T> Any_Virtual& operator=(T const& t){ Clear(); storage_ = new holder<T>(t); return *this; } void Clear(){ if(storage_) delete storage_; } template<class T> T& As(){ return static_cast<holder<T>*>(storage_)->held_; } private: holder_base* storage_; }; // the following demonstrates the use of void pointers // and function pointers to templated operate functions // to safely hide the type enum Operation{ CopyTag, DeleteTag }; template<class T> void Operate(void*const& in, void*& out, Operation op){ switch(op){ case CopyTag: out = new T(*static_cast<T*>(in)); return; case DeleteTag: delete static_cast<T*>(out); } } class Any_VoidPtr{ public: Any_VoidPtr() : object_(0) , operate_(0) {} Any_VoidPtr(Any_VoidPtr const& other) : object_(0) , operate_(other.operate_) { if(other.object_) operate_(other.object_, object_, CopyTag); } template<class T> Any_VoidPtr(T const& t) : object_(new T(t)) , operate_(&Operate<T>) {} ~Any_VoidPtr(){ Clear(); } Any_VoidPtr& operator=(Any_VoidPtr const& other){ Clear(); operate_ = other.operate_; operate_(other.object_, object_, CopyTag); return *this; } template<class T> Any_VoidPtr& operator=(T const& t){ Clear(); object_ = new T(t); operate_ = &Operate<T>; return *this; } void Clear(){ if(object_) operate_(0,object_,DeleteTag); object_ = 0; } template<class T> T& As(){ return *static_cast<T*>(object_); } private: typedef void (*OperateFunc)(void*const&,void*&,Operation); void* object_; OperateFunc operate_; }; int main(){ Any_Virtual a = 6; std::cout << a.As<int>() << std::endl; a = std::string("oh hi!"); std::cout << a.As<std::string>() << std::endl; Any_Virtual av2 = a; Any_VoidPtr a2 = 42; std::cout << a2.As<int>() << std::endl; Any_VoidPtr a3 = a.As<std::string>(); a2 = a3; a2.As<std::string>() +=" - again!"; std::cout <<"a2:" << a2.As<std::string>() << std::endl; std::cout <<"a3:" << a3.As<std::string>() << std::endl; a3 = a; a3.As<Any_Virtual>().As<std::string>() +=" - and yet again!!"; std::cout <<"a:" << a.As<std::string>() << std::endl; std::cout <<"a3->a:" << a3.As<Any_Virtual>().As<std::string>() << std::endl; std::cin.get(); } |
C++中的所有类型擦除技术都是用函数指针(行为)和EDCOX1〔0〕(用于数据)来完成的。"不同"的方法在添加语义糖的方式上完全不同。虚拟函数,例如,只是
1 2 3 4 5 6 | struct Class { struct vtable { void (*dtor)(Class*); void (*func)(Class*,double); } * vtbl }; |
IOW:函数指针。
也就是说,有一种技术我特别喜欢:它是
1 2 3 | { const shared_ptr<void> sp( new A ); } // calls A::~A() here |
当然,这只是通常的
基本上,这些是您的选项:虚拟函数或函数指针。
存储数据和将其与函数关联的方式可能有所不同。例如,您可以存储一个指向基的指针,并让派生类包含数据和虚拟函数实现,或者您可以将数据存储在其他地方(例如,在单独分配的缓冲区中),并且只让派生类提供虚拟函数实现,该实现采用指向数据的
在这种情况下,存储指向基的指针很好地工作,即使数据是单独存储的,如果您希望将多个操作应用于类型已擦除的数据。否则,将以多个函数指针(每种类型的已擦除函数一个)或具有指定要执行的操作的参数的函数结束。
我也会考虑(类似于
在C++0x中,你有EDOCX1,2。
你可以在那里储存你想要的任何东西,只要它足够小并且你处理好了对齐。
Stroustrup,在C++程序设计语言(第四版)第25.3章中指出:
Variants of the technique of using a single runt-time representation for values of a number of types and relying on the (static) type system to ensure that they are used only according to their declared type has been called type erasure.
特别是,如果我们使用模板,则不需要使用虚拟函数或函数指针来执行类型擦除。其他答案中已经提到,根据存储在
斯特劳斯鲁的书中提供的例子同样令人愉快。
考虑实施
通过为
1 2 3 4 5 6 7 8 | template<typename T> class Vector<T*> : private Vector<void*>{ // all the dirty work is done once in the base class only public: // ... // static type system ensures that a reference of right type is returned T*& operator[](size_t i) { return reinterpret_cast<T*&>(Vector<void*>::operator[](i)); } }; |
正如你所看到的,我们有一个强类型的容器,但是EDOCX1,17,EDCOX1,18,EDCOX1,19,…,将共享相同的(C++和二进制)代码来实现,它们的指针类型在EDCOX1×0后面被擦除。
有关类型擦除技术的列表(相当短),以及关于权衡的讨论,请参阅本系列文章:第一部分,第二部分:第三部分:第四部分。
我还没有提到的是adobe.poly和boost.variant,它在某种程度上可以被视为类型擦除。
如马克所说,一个人可以使用铸造的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <iostream> #include <memory> #include <functional> using voidFun = void(*)(std::shared_ptr<void>); template<typename T> void fun(std::shared_ptr<T> t) { std::cout << *t << std::endl; } int main() { std::function<void(std::shared_ptr<void>)> call; call = reinterpret_cast<voidFun>(fun<std::string>); call(std::make_shared<std::string>("Hi there!")); call = reinterpret_cast<voidFun>(fun<int>); call(std::make_shared<int>(33)); call = reinterpret_cast<voidFun>(fun<char>); call(std::make_shared<int>(33)); // Output:, // Hi there! // 33 // ! } |