关于Hinnant的堆栈分配器的问题 [英] Questions about Hinnant's stack allocator
问题描述
我一直在使用Howard Hinnant的堆栈分配器,它的工作方式类似于charm,但实现的一些细节
- 为什么全局运算符
new
和delete
used?allocate()
和deallocate()
成员函数使用:: operator new
和:: operator delete
。类似地,成员函数construct()
使用全局布局new。为什么不允许任何用户定义的全局或类特定的重载? - 为什么对齐设置为硬编码的16字节,而不是
std :: alignment_of< T> ; ? c> $
?
异常规范?这不是不鼓励的(见例如More Effective C ++ Item 14.)?当分配器发生异常时,真的有必要终止和中止吗? c ++> c ++> c ++> c $ c>成员函数将是完美转发(对被调用的构造函数)的理想候选。 - 还需要什么其他修改才能使当前的代码与C ++ 11一致?
- Why are global operators
new
anddelete
used? Theallocate()
anddeallocate()
member functions use::operator new
and::operator delete
respectively. Similarly, the member functionconstruct()
uses the global placement new. Why not allow for any user-defined global or class-specific overloads? - Why is alignment set to hard-coded 16 bytes instead of
std::alignment_of<T>
? - Why do the constructors and
max_size
have athrow()
exception specification? Isn't this discouraged (see e.g. More Effective C++ Item 14.)? Is it really necessary to terminate and abort when an exception occurs in the allocator? Does this change with the new C++11noexcept
keyword? - The
construct()
member function would be an ideal candidate for perfect forwarding (to the constructor that is being called). Is this the way to write C++11 conformant allocators? - What other changes are necessary to make the current code C++11 conformant?
ol>
我一直在使用Howard Hinnant的 stack allocator ,它的工作原理
像一个魅力,但一些细节的实现是有点
我不清楚。
很高兴为你工作。
1。为什么使用全局运算符
new
和delete
?allocate()
和deallocate()
成员函数使用:: operator new
和
:: operator delete
。类似地,成员函数
construct()
使用全局布局new。为什么不允许任何
用户定义的全局或类特定的重载?
没有特殊原因。随意修改这个代码以任何方式最适合你。这意味着更多的例子,它绝不是完美的。唯一的要求是,allocator和deallocator提供正确对齐的内存,并且构造成员构造一个参数。
在C ++ 11中,成员是可选的。如果您在提供 allocator_traits
的环境中运行,我建议您从分配器中删除它们。要找出,只需删除它们,看看是否仍然编译。
2。为什么对齐设置为硬编码的16个字节,而不是
std :: alignment_of< T>
?
std :: alignment_of< T>
可能会正常工作。那天我可能是偏执的。
3。为什么构造函数和
max_size
有一个throw()
异常规范?这不是不鼓励(见例如更有效的C ++
项目14.)?当分配器中发生
异常时,真的有必要终止和中止吗?这是否会随着新的C ++ 11
noexcept
关键字而改变?
这些成员不会永远扔。对于C ++ 11,我应该更新到 noexcept
。在C ++ 11中,使用 noexcept
来装饰事物变得更加重要,尤其是特殊成员。在C ++ 11中,可以检测表达式是否不是。代码可以根据这个答案分支。已知为nothrow的代码更可能导致通用代码分支到更有效的路径。 std :: move_if_noexcept
是C ++ 11中的规范示例。
不要使用 throw(type1,type2)
ever。它已在C ++ 11中弃用。
当你真的想说: throw()
这永远不会抛出,如果我错了,终止程序,所以我可以调试它。 throw()
在C ++ 11中也被弃用,但是有一个插入替换: noexcept
p>
4。成员函数将成为完美转发(被调用的构造函数)的理想候选者。
construct()
这是
是写C ++ 11一致分配器的方法吗?
是的。但是 allocator_traits
会为你做。让它。 std :: lib已经为你调试了这个代码。 C ++ 11容器将调用 allocator_traits< YourAllocator> :: construct(your_allocator,pointer,args ...)
。如果你的分配器实现了这些函数,allocator_traits将调用你的实现,否则调用一个调试,高效的默认实现。
5。还需要什么其他修改才能使当前代码C ++ 11符合?
告诉你的事实,这个分配器不是真正的C ++ 03或C ++ 11一致。当复制分配器时,原始和副本应该彼此相等。在这个设计中,这是永远不是真的。然而,这个东西仍然只是碰巧在许多上下文中工作。
如果你想使它严格符合,你需要另一个间接级别,使副本指向同一
除了C ++ 11的分配器,C ++ 11分配器比C ++ 98/03分配器更容易构建。以下是必须执行的最低限度: 模板< class T&
class MyAllocator
{
public:
typedef T value_type;
MyAllocator()noexcept; // only required if used
MyAllocator(const MyAllocator&)noexcept; // copies must be equal
MyAllocator(MyAllocator&&)noexcept; //如果copy ctor足够好就不需要
template< class U>
MyAllocator(const MyAllocator< U>& u)noexcept; // requires:* this == MyAllocator(u)
value_type * allocate(std :: size_t);
void deallocate(value_type *,std :: size_t)noexcept;
};
template< class T,class U>
bool operator ==(const MyAllocator< T& const; MyAllocator< U>>)noexcept;
template< class T,class U>
bool operator!=(const MyAllocator< T& const; MyAllocator< U>>)noexcept;
您可以选择考虑使用 MyAllocator
将以下嵌套类型放在分配器中:
typedef std :: true_type propagate_on_container_swap;
还有一些其他的旋钮,你可以调整C ++ 11分配器。
上面我注意到了我的堆栈分配器不符合,因为副本不相等的事实。我决定更新这个分配器到一个符合的C ++ 11分配器。新的分配器称为 short_allocator ,并记录在此处。
short_allocator 不同于堆栈分配器,因为内部缓冲区不再是分配器内部的,而是现在是一个单独的竞技场对象,可以位于本地堆栈,或给定线程或静态存储持续时间。 arena
不是线程安全的,所以小心这一点。你可以让它线程安全,如果你想,但是有递减的回报(最终你会重塑malloc)。
这是符合,因为分配器的副本都指向相同的外部 arena
。请注意, N
的单位现在是字节,而不是 T
的数字。
可以通过添加C ++ 98/03模板(typedefs,构造成员,销毁成员等)将这个C ++ 11分配器转换为C ++ 98/03分配器。 )。一个繁琐但简单的任务。
这个问题的答案为新的 short_allocator 保持不变。
I've been using Howard Hinnant's stack allocator and it works like a charm, but some details of the implementation are a little unclear to me.
I've been using Howard Hinnant's stack allocator and it works like a charm, but some details of the implementation are a little unclear to me.
Glad it's been working for you.
1. Why are global operators
new
anddelete
used? Theallocate()
anddeallocate()
member functions use::operator new
and::operator delete
respectively. Similarly, the member functionconstruct()
uses the global placement new. Why not allow for any user-defined global or class-specific overloads?
There's no particular reason. Feel free to modify this code in whatever way works best for you. This was meant to be more of an example, and it is by no means perfect. The only requirements are that the allocator and deallocator supply properly aligned memory, and that the construct member constructs an argument.
In C++11, the construct (and destroy) members are optional. I would encourage you to remove them from the allocator if you're operating in an environment that supplies allocator_traits
. To find out, just remove them and see if things still compile.
2. Why is alignment set to hard-coded 16 bytes instead of
std::alignment_of<T>
?
std::alignment_of<T>
would probably work fine. I was probably being paranoid that day.
3. Why do the constructors and
max_size
have athrow()
exception specification? Isn't this discouraged (see e.g. More Effective C++ Item 14.)? Is it really necessary to terminate and abort when an exception occurs in the allocator? Does this change with the new C++11noexcept
keyword?
These members just won't ever throw. For C++11 I should update them to noexcept
. In C++11 it becomes more important to decorate things with noexcept
, especially special members. In C++11 one can detect whether an expression is nothrow or not. Code can branch depending on that answer. Code that is known to be nothrow is more likely to cause generic code to branch to a more efficient path. std::move_if_noexcept
is the canonical example in C++11.
Don't use throw(type1, type2)
ever. It has been deprecated in C++11.
Do use throw()
when you really want to say: This will never throw, and if I'm wrong, terminate the program so I can debug it. throw()
is also deprecated in C++11, but has a drop-in replacement: noexcept
.
4. The
construct()
member function would be an ideal candidate for perfect forwarding (to the constructor that is being called). Is this the way to write C++11 conformant allocators?
Yes. However allocator_traits
will do it for you. Let it. The std::lib has already debugged that code for you. C++11 containers will call allocator_traits<YourAllocator>::construct(your_allocator, pointer, args...)
. If your allocator implements these functions, allocator_traits will call your implementation, else it calls a debugged, efficient, default implementation.
5. What other changes are necessary to make the current code C++11 conformant?
To tell you the truth, this allocator isn't really C++03 or C++11 conformant. When you copy an allocator, the original and the copy are supposed to be equal to each other. In this design, that is never true. However this thing still just happens to work in many contexts.
If you want to make it strictly conforming, you need another level of indirection such that copies will point to the same buffer.
Aside from that, C++11 allocators are so much easier to build than C++98/03 allocators. Here's the minimum you must do:
template <class T>
class MyAllocator
{
public:
typedef T value_type;
MyAllocator() noexcept; // only required if used
MyAllocator(const MyAllocator&) noexcept; // copies must be equal
MyAllocator(MyAllocator&&) noexcept; // not needed if copy ctor is good enough
template <class U>
MyAllocator(const MyAllocator<U>& u) noexcept; // requires: *this == MyAllocator(u)
value_type* allocate(std::size_t);
void deallocate(value_type*, std::size_t) noexcept;
};
template <class T, class U>
bool operator==(const MyAllocator<T>&, const MyAllocator<U>&) noexcept;
template <class T, class U>
bool operator!=(const MyAllocator<T>&, const MyAllocator<U>&) noexcept;
You might optionally consider making MyAllocator
Swappable and put the following nested type in the allocator:
typedef std::true_type propagate_on_container_swap;
There's a few other knobs like that you can tweak on C++11 allocators. But all of the knobs have reasonable defaults.
Update
Above I note that my stack allocator is not conforming due to the fact that copies are not equal. I've decided to update this allocator to a conforming C++11 allocator. The new allocator is called short_allocator and is documented here.
The short_allocator differs from the stack allocator in that the "internal" buffer is no longer internal to the allocator, but is now a separate "arena" object that can be located on the local stack, or given thread or static storage duration. The arena
isn't thread safe though so watch out for that. You could make it thread safe if you wanted to, but that has diminishing returns (eventually you'll reinvent malloc).
This is conforming because copies of allocators all point to the same external arena
. Note that the unit of N
is now bytes, not number of T
.
One could convert this C++11 allocator to a C++98/03 allocator by adding the C++98/03 boiler-plate (the typedefs, the construct member, the destroy member, etc.). A tedious, but straightforward task.
The answers to this question for the new short_allocator remain unchanged.
这篇关于关于Hinnant的堆栈分配器的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!