types擦除技术
(对于types擦除,我的意思是隐藏部分或全部关于类的types信息,有点像Boost.Any 。)
我想得到一种types的删除技术,同时也分享这些,我知道的。 我希望find一些疯狂的技术,有人在最黑暗的时刻想到。 🙂
我所知道的第一个也是最明显也是最常用的方法是虚拟function。 只需在基于类的层次结构中隐藏类的实现即可。 许多Boost库都这样做,例如Boost.Any这样做是为了隐藏你的types,而Boost.Shared_ptr这样做是为了隐藏(de)分配机制。
然后有模板函数的函数指针的选项,同时将实际的对象保存在void*
指针中,就像Boost.Function一样,可以隐藏函数的实际types。 示例实现可以在问题的最后find。
所以,对于我的实际问题:
你知道什么其他types的擦除技术? 如有可能,请提供示例代码,用例,与他们的经验以及可能的链接以供进一步阅读。
编辑
(因为我不确定这个答案是否会增加,或者只是编辑问题,我只会做更安全的问题。)
另一个很好的技术,隐藏实际types的东西, 没有虚函数或void*
摆弄,是一个GMan在这里雇用,与我的问题是如何工作的。
示例代码:
#include <iostream> #include <string> // NOTE: The class name indicates the underlying type erasure technique // this behaves like the Boost.Any type wrt 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 ++中所有types的擦除技术都是用函数指针(行为)和void*
(用于数据)完成的。 “不同的”方法在添加语义糖的方式上有所不同。 虚拟function,例如,仅仅是语义上的糖
struct Class { struct vtable { void (*dtor)(Class*); void (*func)(Class*,double); } * vtbl };
喵:函数指针。
也就是说,我特别喜欢一种技术:它是shared_ptr<void>
,仅仅是因为它让人不知道你可以这样做:你可以将任何数据存储在shared_ptr<void>
,并且仍然有正确的析构函数在最后调用,因为shared_ptr
构造函数是一个函数模板,并且将使用默认情况下为创builddeleter传递的实际对象的types:
{ const shared_ptr<void> sp( new A ); } // calls A::~A() here
当然,这只是通常的void*
/ function-pointertypes的擦除,但是非常方便的打包。
从根本上说,这些是你的select:虚函数或函数指针。
如何存储数据并将其与函数关联可能会有所不同。 例如,你可以存储一个指向基类的指针,派生类包含数据和虚拟函数的实现,或者你可以将数据存储在其他地方(例如在一个单独分配的缓冲区中),只需派生类提供虚拟函数的实现,其中有一个void*
指向数据。 如果将数据存储在单独的缓冲区中,则可以使用函数指针而不是虚函数。
如果有多个操作要应用到types擦除的数据,即使数据是单独存储的,存储指向基础的指针也能很好地工作。 否则,最终会有多个函数指针(每个types擦除函数都有一个指针),或者带有指定要执行的操作的参数的函数。
我也会考虑(类似于void*
)使用“raw storage”: char buffer[N]
。
在C ++ 0x你有std::aligned_storage<Size,Align>::type
为此。
你可以在里面存储任何你想要的东西,只要它足够小,并且正确地处理alignment。
请看这一系列的post,查找一个(相当短的)types删除技术列表和关于权衡的讨论: 第一部分 , 第二部分 , 第三部分 , 第四部分 。
我还没有看到提到的那个是Adobe.Poly和Boost.Variant ,这在某种程度上可以被认为是一种types的擦除。
在C ++编程语言(第四版)§25.3中的Stroustrup指出:
使用单个runt-time表示来表示多个types的值并依靠(静态)types系统确保它们仅根据其声明的types使用的技术的变体被称为types擦除 。
特别是,如果使用模板,则不需要使用虚函数或函数指针来执行types擦除。 已经在其他答案中提到过,根据存储在std::shared_ptr<void>
的types正确析构函数调用的情况就是一个例子。
Stroustrup书中提供的例子同样令人愉快。
考虑实现template<class T> class Vector
,这是一个沿着std::vector
的容器。 当你将使用你的Vector
与许多不同的指针types,因为它经常发生,编译器会为每个指针types生成不同的代码。
通过为void*
指针定义Vector的专门化,然后将此专用化用作Vector<T*>
的所有其他types的通用基本实现,可以防止此代码膨胀 T
:
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)); } };
正如你所看到的,我们有一个强types的容器,但Vector<Animal*>
, Vector<Dog*>
, Vector<Cat*>
,…将共享相同的(C ++ 和二进制)代码,指针types擦除 void*
后面。
如Marc所述,可以使用cast std::shared_ptr<void>
。 例如,将types存储在函数指针中,将其转换并存储在一个types的函数中:
#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 // ! }