C++ 类型擦除技术
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5450159/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Type erasure techniques
提问by Xeo
(With type erasure, I mean hiding some or all of the type information regarding a class, somewhat like Boost.Any.)
I want to get a hold of type erasure techniques, while also sharing those, which I know of. My hope is kinda to find some crazy technique that somebody thought of in his/her darkest hour. :)
(对于类型擦除,我的意思是隐藏有关类的部分或全部类型信息,有点像Boost.Any。)
我想掌握类型擦除技术,同时也分享我所知道的那些技术。我希望找到一些有人在他/她最黑暗的时刻想到的疯狂技巧。:)
The first and most obvious, and commonly taken approach, that I know, are virtual functions. Just hide the implementation of your class inside an interface based class hierarchy. Many Boost libraries do this, for example Boost.Anydoes this to hide your type and Boost.Shared_ptrdoes this to hide the (de)allocation mechanic.
据我所知,第一个也是最明显和最常用的方法是虚函数。只需将类的实现隐藏在基于接口的类层次结构中。许多 Boost 库都这样做,例如Boost.Any这样做是为了隐藏您的类型,而Boost.Shared_ptr这样做是为了隐藏(解除)分配机制。
Then there is the option with function pointers to templated functions, while holding the actual object in a void*
pointer, like Boost.Functiondoes to hide the real type of the functor. Example implementations can be found at the end of the question.
然后是带有指向模板函数的函数指针的选项,同时将实际对象保存在void*
指针中,就像Boost.Function那样隐藏函子的真实类型。示例实现可以在问题的末尾找到。
So, for my actual question:
What other type erasure techniques do you know of? Please provide them, if possible, with an example code, use cases, your experience with them and maybe links for further reading.
那么,对于我的实际问题:
您还知道哪些其他类型的擦除技术?如果可能,请向他们提供示例代码、用例、您对他们的体验以及进一步阅读的链接。
Edit
(Since I wasn't sure wether to add this as an answer, or just edit the question, I'll just do the safer one.)
Another nice technique to hide the actual type of something withoutvirtual functions or void*
fiddling, is the one GMan employs here, with relevance to my questionon how exactly this works.
编辑
(因为我不确定是否将其添加为答案,或者只是编辑问题,我只会做更安全的。)
另一种很好的技术来隐藏没有虚函数或void*
摆弄的东西的实际类型,是一位 GMan 在这里雇用,与我关于这究竟是如何工作的问题有关。
Example code:
示例代码:
#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();
}
回答by Marc Mutz - mmutz
All type erasure techniques in C++ are done with function pointers (for behaviour) and void*
(for data). The "different" methods simply differ in the way they add semantic sugar. Virtual functions, e.g., are just semantic sugar for
C++ 中的所有类型擦除技术都是通过函数指针(用于行为)和void*
(用于数据)完成的。“不同”的方法只是在添加语义糖的方式上有所不同。虚函数,例如,只是语义糖
struct Class {
struct vtable {
void (*dtor)(Class*);
void (*func)(Class*,double);
} * vtbl
};
iow: function pointers.
iow:函数指针。
That said, there's one technique I particularly like, though: It's shared_ptr<void>
, simply because it blows the minds off of people who don't know you can do this: You can store any data in a shared_ptr<void>
, and still have the correct destructor called at the end, because the shared_ptr
constructor is a function template, and will use the type of the actual object passed for creating the deleter by default:
也就是说,我特别喜欢一种技术:它是shared_ptr<void>
,只是因为它让那些不知道您可以这样做的人大吃一惊:您可以将任何数据存储在 a 中shared_ptr<void>
,并且仍然可以在最后,因为shared_ptr
构造函数是一个函数模板,默认会使用传入的实际对象的类型来创建删除器:
{
const shared_ptr<void> sp( new A );
} // calls A::~A() here
Of course, this is just the usual void*
/function-pointer type erasure, but very conveniently packaged.
当然,这只是通常的void*
/function-pointer 类型擦除,但非常方便地打包。
回答by Anthony Williams
Fundamentally, those are your options: virtual functions or function pointers.
从根本上说,这些是您的选择:虚函数或函数指针。
How you store the data and associate it with the functions can vary. For example, you could store a pointer-to-base, and have the derived class contain the data andthe virtual function implementations, or you could store the data elsewhere (e.g. in a separately allocated buffer), and just have the derived class provide the virtual function implementations, which take a void*
that points to the data. If you store the data in a separate buffer, then you could use function pointers rather than virtual functions.
您存储数据并将其与函数关联的方式可能会有所不同。例如,您可以存储一个指向基类的指针,并让派生类包含数据和虚函数实现,或者您可以将数据存储在其他地方(例如,在单独分配的缓冲区中),并且只让派生类提供虚函数实现,它采用void*
指向数据的 。如果将数据存储在单独的缓冲区中,则可以使用函数指针而不是虚拟函数。
Storing a pointer-to-base works well in this context, even if the data is stored separately, if there are multiple operations that you wish to apply to your type-erased data. Otherwise you end up with multiple function pointers (one for each of the type-erased functions), or functions with a parameter that specifies the operation to perform.
存储指向基的指针在这种情况下效果很好,即使数据是单独存储的,如果您希望将多个操作应用于类型擦除数据。否则,您最终会得到多个函数指针(每个类型擦除函数一个),或者带有指定要执行的操作的参数的函数。
回答by Matthieu M.
I would also consider (similar to void*
) the use of "raw storage": char buffer[N]
.
我也会考虑(类似void*
)使用的“原始存储”: char buffer[N]
。
In C++0x you have std::aligned_storage<Size,Align>::type
for this.
在 C++0x 中,你有std::aligned_storage<Size,Align>::type
这个。
You can store anything you want in there, as long as it's small enough and you deal with the alignment properly.
你可以在里面存储任何你想要的东西,只要它足够小并且你正确处理对齐。
回答by Paolo M
Stroustrup, in The C++ programming language (4th edition) §25.3, states:
Stroustrup 在C++ 编程语言(第 4 版)第 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.
对多种类型的值使用单个运行时表示并依靠(静态)类型系统来确保仅根据其声明的类型使用它们的技术的变体被称为类型擦除。
In particular, no use of virtual functions or function pointersis needed to perform type erasure if we use templates. The case, already mentioned in other answers, of the correct destructor call according to the type stored in a std::shared_ptr<void>
is an example of that.
特别是,如果我们使用模板,则不需要使用虚函数或函数指针来执行类型擦除。已经在其他答案中提到的根据存储在 a 中的类型正确调用析构函数的情况std::shared_ptr<void>
就是一个例子。
The example provided in Stroustrup's book is just as enjoyable.
Stroustrup 书中提供的示例同样令人愉快。
Think about implementing template<class T> class Vector
, a container along the lines of std::vector
. When you will use your Vector
with a lot of different pointers types, as it often happens, the compiler will supposedly generate different code for every pointer type.
考虑实现template<class T> class Vector
一个类似std::vector
. 当您将使用Vector
许多不同的指针类型时,正如经常发生的那样,编译器应该会为每种指针类型生成不同的代码。
This code bloatcan be prevented by defining a specialization of Vectorfor void*
pointers and then using this specialization as a common base implementation of Vector<T*>
for all others types T
:
通过为指针定义Vector的特化,然后将此特化用作所有其他类型的公共基本实现,可以防止此代码膨胀:void*
Vector<T*>
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)); }
};
As you can see, we have a strongly typed container but Vector<Animal*>
, Vector<Dog*>
, Vector<Cat*>
, ..., will share the same (C++ andbinary) code for the implementation, having their pointer type erasedbehind void*
.
如您所见,我们有一个强类型容器,但是Vector<Animal*>
, Vector<Dog*>
, Vector<Cat*>
, ... 将共享相同的(C++和二进制)代码用于实现,将它们的指针类型擦除在void*
.
回答by Andrzej
See this series of posts for a (fairly short) list of type erasure techniques and the discussion about the trade-offs: Part I, Part II, Part III, Part IV.
有关类型擦除技术的(相当短的)列表以及有关权衡的讨论,请参阅本系列文章: 第 I部分、 第 II部分、 第 III部分、 第 IV 部分。
The one I haven't seen mentioned yet is Adobe.Poly, and Boost.Variant, which can be considered a type erasure to some extent.
我还没有看到提到的是Adobe.Poly和Boost.Variant,这在某种程度上可以认为是一种类型擦除。
回答by Janek Olszak
As stated by Marc, one can use cast std::shared_ptr<void>
.
For example store the typein a function pointer, cast it and store in a functor of only one type:
正如 Marc 所说,可以使用 cast std::shared_ptr<void>
。例如,将类型存储在函数指针中,将其强制转换并存储在只有一种类型的函子中:
#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
// !
}