C++ 中不同大小的不同行为(火息源代码)
当我查看 firebreath 的源代码时遇到一个困惑的问题( src/ScriptingCore/Variant.h)
// function pointer table
struct fxn_ptr_table {
const std::type_info& (*get_type)();
void (*static_delete)(void**);
void (*clone)(void* const*, void**);
void (*move)(void* const*,void**);
bool (*less)(void* const*, void* const*);
};
// static functions for small value-types
template<bool is_small>
struct fxns
{
template<typename T>
struct type {
static const std::type_info& get_type() {
return typeid(T);
}
static void static_delete(void** x) {
reinterpret_cast<T*>(x)->~T();
}
static void clone(void* const* src, void** dest) {
new(dest) T(*reinterpret_cast<T const*>(src));
}
static void move(void* const* src, void** dest) {
reinterpret_cast<T*>(dest)->~T();
*reinterpret_cast<T*>(dest) = *reinterpret_cast<T const*>(src);
}
static bool lessthan(void* const* left, void* const* right) {
T l(*reinterpret_cast<T const*>(left));
T r(*reinterpret_cast<T const*>(right));
return l < r;
}
};
};
// static functions for big value-types (bigger than a void*)
template<>
struct fxns<false>
{
template<typename T>
struct type {
static const std::type_info& get_type() {
return typeid(T);
}
static void static_delete(void** x) {
delete(*reinterpret_cast<T**>(x));
}
static void clone(void* const* src, void** dest) {
*dest = new T(**reinterpret_cast<T* const*>(src));
}
static void move(void* const* src, void** dest) {
(*reinterpret_cast<T**>(dest))->~T();
**reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src);
}
static bool lessthan(void* const* left, void* const* right) {
return **reinterpret_cast<T* const*>(left) < **reinterpret_cast<T* const*>(right);
}
};
};
template<typename T>
struct get_table
{
static const bool is_small = sizeof(T) <= sizeof(void*);
static fxn_ptr_table* get()
{
static fxn_ptr_table static_table = {
fxns<is_small>::template type<T>::get_type
, fxns<is_small>::template type<T>::static_delete
, fxns<is_small>::template type<T>::clone
, fxns<is_small>::template type<T>::move
, fxns<is_small>::template type<T>::lessthan
};
return &static_table;
}
};
问题是为什么大值类型(大于 void*)的静态函数的实现与小值类型不同。
例如,对于小值类型,static_delete 只是调用 T 实例的析构函数,而对于大值类型,则使用“删除”。
有什么技巧吗?提前致谢。
I encounter a confused question when I go through the source code of firebreath (src/ScriptingCore/Variant.h)
// function pointer table
struct fxn_ptr_table {
const std::type_info& (*get_type)();
void (*static_delete)(void**);
void (*clone)(void* const*, void**);
void (*move)(void* const*,void**);
bool (*less)(void* const*, void* const*);
};
// static functions for small value-types
template<bool is_small>
struct fxns
{
template<typename T>
struct type {
static const std::type_info& get_type() {
return typeid(T);
}
static void static_delete(void** x) {
reinterpret_cast<T*>(x)->~T();
}
static void clone(void* const* src, void** dest) {
new(dest) T(*reinterpret_cast<T const*>(src));
}
static void move(void* const* src, void** dest) {
reinterpret_cast<T*>(dest)->~T();
*reinterpret_cast<T*>(dest) = *reinterpret_cast<T const*>(src);
}
static bool lessthan(void* const* left, void* const* right) {
T l(*reinterpret_cast<T const*>(left));
T r(*reinterpret_cast<T const*>(right));
return l < r;
}
};
};
// static functions for big value-types (bigger than a void*)
template<>
struct fxns<false>
{
template<typename T>
struct type {
static const std::type_info& get_type() {
return typeid(T);
}
static void static_delete(void** x) {
delete(*reinterpret_cast<T**>(x));
}
static void clone(void* const* src, void** dest) {
*dest = new T(**reinterpret_cast<T* const*>(src));
}
static void move(void* const* src, void** dest) {
(*reinterpret_cast<T**>(dest))->~T();
**reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src);
}
static bool lessthan(void* const* left, void* const* right) {
return **reinterpret_cast<T* const*>(left) < **reinterpret_cast<T* const*>(right);
}
};
};
template<typename T>
struct get_table
{
static const bool is_small = sizeof(T) <= sizeof(void*);
static fxn_ptr_table* get()
{
static fxn_ptr_table static_table = {
fxns<is_small>::template type<T>::get_type
, fxns<is_small>::template type<T>::static_delete
, fxns<is_small>::template type<T>::clone
, fxns<is_small>::template type<T>::move
, fxns<is_small>::template type<T>::lessthan
};
return &static_table;
}
};
The question is why the implementation of static functions for the big value-types (bigger than void*) is different from the small ones.
For example, static_delete for small value-type is just to invoke destructor on T instance, while that for big value-type is to use 'delete'.
Is there some trick? Thanks in advance.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
看起来Firebreath为其小对象使用了专用的内存池,而大对象通常在堆中分配。因此有不同的行为。例如,请注意
clone()
中小对象的new
位置:这会在指定的内存位置创建新对象,而不分配它。当您使用放置new
创建对象时,必须在释放内存之前显式调用其析构函数,这就是static_delete()
所做的。内存实际上并未被释放,因为正如我所说,它看起来像是正在使用专用的内存池。内存管理必须在其他地方执行。这种内存池是常见的针对小对象的优化。
It looks like Firebreath uses a dedicated memory pool for its small objects, while large objects are allocated normally in the heap. Hence the different behaviour. Notice the placement
new
inclone()
for small objects, for instance: this creates the new object in a specified memory location without allocating it. When you create an object using placementnew
, you must explicitly call the destructor on it before deallocating memory, and this is whatstatic_delete()
does.Memory is not actually deallocated because, as I say, it looks like a dedicated memory pool is in use. Memory management must be performed somewhere else. This kind of memory pool is a common optimisation for small objects.
内部文档怎么说?如果作者没有
记录下来,他可能不认识自己。
从代码来看,小对象的接口与
对于大型物体;您为小对象传递的指针是
指向对象本身的指针,其中作为一个大对象传递的指针
object 是一个指向该对象的指针。
然而,作者似乎不太了解 C++(我会
避免使用这样的代码)。例如,在
move
中,他明确地破坏对象,然后分配给它:这保证是未定义的
行为,除了最简单的情况之外,可能无法可靠地工作
内置类型。小物体与大物体的区别很大程度上是
不相关的;一些“小”物体的成本可能相当高
复制。当然,考虑到这里的一切都是模板,
绝对没有理由使用
void*
来做任何事情。What does the internal documentation say? If the author hasn't
documented it, he probably doesn't know himself.
Judging from the code, the interface to small objects is different than
that to large objects; the pointer you pass for a small object is a
pointer to the object itself, where as the one you pass for a large
object is a pointer to a pointer to the object.
The author, however, doesn't seem to know C++ very well (and I would
avoid using any code like this). For example, in
move
, he explicitlydestructs the object, then assigns to it: this is guaranteed undefined
behavior, and probably won't work reliably for anything but the simplest
built-in types. Also the distinction small vs. large objects is largely
irrelevant; some “small” objects can be quite expensive to
copy. And of course, given that everything here is a template anyway,
there's absolutely no reason to use
void*
for anything.我已经编辑了您的问题以包含原始源文件的链接,因为显然这里回答的大多数人都没有阅读它以了解实际发生的情况。我承认这可能是 FireBreath 中最令人困惑的代码之一;当时,我试图避免使用 boost,这非常有效。
从那时起,我考虑切换到 boost::any (对于那些渴望建议它的人,不,boost::variant 不起作用,我不会在这里解释为什么;如果你真的关心,请问另一个问题)但是我们对这个类进行了相当多的定制,以使其完全符合我们的需要,而 boost::any 很难以类似的方式定制。最重要的是,我们一直遵循古老的格言:如果它没有坏,就不要修理它!
首先,你应该知道有几位 C++ 专家已经审阅过这段代码;是的,它使用了一些许多人认为可疑的做法,但它们经过了非常仔细的考虑,并且它们在 FireBreath 支持的编译器上是一致且可靠的。我们已经使用 valgrind、视觉泄漏检测器、LeakFinder 和 Rational Purify 进行了广泛的测试,并且从未在该代码中发现任何泄漏。这有点令人困惑;令我惊讶的是,不懂代码的人会认为作者不懂 C++。在这种情况下,Christopher Diggins(他编写了您引用的代码以及原始的 cdiggins::any 类)似乎非常了解 C++,他能够编写此代码这一事实证明了这一点。该代码在内部使用并且经过高度优化——实际上可能超出了 FireBreath 的需要。然而,它对我们很有帮助。
我将尽力以我所记得的最好方式解释你的问题的答案;请记住,我没有太多时间,而且我已经有一段时间没有真正深入研究这个问题了。 “小”类型使用不同静态类的主要原因是“小”类型几乎是内置类型; int、char、long 等。任何大于 void* 的东西都被假定为某种类型的对象。这是一种优化,允许它尽可能重用内存,而不是删除并重新分配内存。
如果你并排查看代码,就会清楚得多。如果您查看删除和克隆,您会发现在“大”对象上它会动态分配内存;它在删除中调用“删除”,在克隆中它使用常规的“新”。在“小”版本中,它只是在内部存储内存并重用它;它永远不会“删除”内存,它只是在其内部拥有的内存上调用正确类型的析构函数或构造函数。再次强调,这样做只是为了提高效率。在两种类型上的移动中,它都会调用旧对象的析构函数,然后分配新对象数据。
对象本身存储为 void* 因为我们实际上不知道该对象是什么类型;事实上,要获取对象,您必须指定类型。这是允许容器保存绝对任何类型的数据的一部分。这就是那里有如此多的reinterpret_cast调用的原因——很多人看到这一点并说“哦,不!作者一定是一无所知!”但是,当您需要取消引用 void* 时,这正是您要使用的运算符。
不管怎样,综上所述,cdiggins 今年实际上已经推出了他的任何课程的新版本;我需要看一下它,并且可能会尝试将其替换为当前的。诀窍是我已经定制了当前版本(主要是添加一个比较运算符,以便可以将其放入 STL 容器中并添加 Convert_cast),因此我需要确保我足够了解新版本,以便安全地执行此操作。
希望有帮助;我从中得到的文章在这里: http://www.codeproject.com/KB/ cpp/dynamic_typing.aspx
请注意,该文章已更新,似乎无法再使用原始文章来访问旧文章。
编辑
自从我写这篇文章以来,我们已经确认了旧变体类的一些问题,并且它已被更新并替换为利用 boost::any 的变体类。感谢 dougma 在这方面所做的大部分工作。 FireBreath 1.7(截至撰写本文时的当前主分支)包含该修复。
I have edited your question to include a link to the original source file, since obviously most of those answering here have not read it to see what is actually going on. I admit that this is probably one of the most confusing pieces of code in FireBreath; at the time, I was trying to avoid using boost and this has worked really well.
Since then I've considered switching to boost::any (for those itching to suggest it, no, boost::variant wouldn't work and I'm not going to explain why here; ask another question if you really care) but we have customized this class a fair amount to make it exactly what we need and boost::any would be difficult to customize in a similar manner. More than anything, we've been following the old axim: if it ain't broke, don't fix it!
First of all, you should know that several C++ experts have gone over this code; yes, it uses some practices that many consider dubious, but they are very carefully considered and they are consistent and reliable on the compilers supported by FireBreath. We have done extensive testing with valgrind, visual leak detector, LeakFinder, and Rational Purify and have never found any leaks in this code. It is more than a bit confusing; it's amazing to me that people who don't understand code assume the author doesn't know C++. In this case, Christopher Diggins (who wrote the code you quoted and the original cdiggins::any class that this is taken from) seems to know C++ extremely well as evidenced by the fact that he was able to write this code. The code is used internally and is highly optimized -- perhaps more than FireBreath needs, in fact. However, it has served us well.
I will try to explain the answer to your question as best I remember; keep in mind that I don't have a lot of time and it's been awhile since I really dug in deep with this. The main reason for "small" types using a different static class is that "small" types are pretty much built-in types; an int, a char, a long, etc. Anything bigger than void* is assumed to be an object of some sort. This is an optimization to allow it to reuse memory whenever possible rather than deleting and reallocating it.
If you look at the code side-by-side it's a lot clearer. If you look at delete and clone you'll see that on "large" objects it's dynamically allocating the memory; it calls "delete" in delete and in clone it uses a regular "new". In the "small" version it just stores the memory internally and reuses it; it never "delete"s the memory, it just calls the destructor or the constructor of the correct type on the memory that it has internally. Again, this is just done for the sake of efficiency. In move on both types it calls the destructor of the old object and then assigns the new object data.
The object itself is stored as a void* because we don't actually know what type the object will be; to get the object back out you have to specify the type, in fact. This is part of what allows the container to hold absolutely any type of data. That is the reason there are so many reinterpret_cast calls there -- many people see that and say "oh, no! The author must be clueless!" However, when you have a void* that you need to dereference, that's exactly the operator that you would use.
Anyway, all of that said, cdiggins has actually put out a new version of his any class this year; I'll need to take a look at it and probably will try to pull it in to replace the current one. The trick is that I have customized the current one (primarily to add a comparison operator so it can be put in a STL container and to add convert_cast) so I need to make sure I understand the new version well enough to do that safely.
Hope that helps; the article I got it from is here: http://www.codeproject.com/KB/cpp/dynamic_typing.aspx
Note that the article has been updated and it doesn't seem to be possible to get to the old one with the original anymore.
EDIT
Since I wrote this we have confirmed some issues with the old variant class and it has been updated and replaced with one that utilizes boost::any. Thanks to dougma for most of the work on this. FireBreath 1.7 (current master branch as of the time of this writing) contains that fix.