STL compatible allocators with _Ty local data does not compile.
We added allocator for std::vector storing short vectors in the allocator (on stack) instead of allocating on heap. This allocator compiled on VS8, not on Orcas.
Error:
error C2512: 'std::_Aux_cont' : no appropriate default constructor available.
Compilation is OK if _SECURE_SCL 0 is defined.
See that a new allocator created in _Delete_aux. This creates the problem.
Don't see how to avoid this error.
Usage:
std::vector<int,ScVectorStackAllocator<int, 3>>xxTest;
Cheers
Ditlef
template <class_Ty,size_tstack_size=4>
classScVectorStackAllocator :publicstd::_Allocator_base<_Ty>
{
_Tym_mem[stack_size];// Internal memory for short vector.
public:
typedefstd::_Allocator_base<_Ty>_Mybase;
typedeftypename_Mybase::value_typevalue_type;
// Standard definitions, borrowed from the default VC++ allocator
typedef
value_type*pointer;typedef
value_type&reference;typedefconst
value_type*const_pointer;typedefconst
value_type&const_reference;typedef
size_tsize_type;typedef
ptrdiff_tdifference_type;template
<class
_Other>struct
rebind{
// convert an allocator<_Ty> to an allocator <_Other>
typedef
ScVectorStackAllocator<_Other,stack_size>other;};
pointeraddress(reference_Val)const
{return
&_Val;}const_pointeraddress(const_reference_Val)const
{return
&_Val;}ScVectorStackAllocator()_THROW0(){
// construct default allocator (do nothing)
}
ScVectorStackAllocator(const
ScVectorStackAllocator&)_THROW0(){
// construct by copying (do nothing)
}
template
<class
_Other/*, size_t otherSize*/
>ScVectorStackAllocator(const
ScVectorStackAllocator<_Other,stack_size>&)_THROW0(){
// construct from a related allocator (do nothing)
}
template
<class
_Other/*, size_t otherSize*/
>ScVectorStackAllocator<_Ty,stack_size>&operator
=(const
ScVectorStackAllocator<_Other,stack_size>&){
// assign from a related allocator (do nothing)
return
*this
;}
void
deallocate(void
*_Ptr,size_type){
// Only delete memory if allocated.
//if (m_mem != _Ptr)
operatordelete
(_Ptr);}
pointerallocate(size_type_Count){
// allocate array of _Count elements
// Return pointer to internal memory if short, else standard alloc.
//if (_Count <= stack_size) return (pointer)m_mem;
return
(std::_Allocate(_Count, (pointer)0));}
pointerallocate(size_type_Count,constvoid
_FARQ *){
// allocate array of _Count elements, ignore hint
return
(allocate(_Count));}
void
construct(pointer_Ptr,const
_Ty&_Val){
// construct object at _Ptr with value _Val
// Only construct if not in pre-allocated memory
if
(_Ptr != &_Val)std::_Construct(_Ptr,_Val);}
void
destroy(pointer_Ptr){
// destroy object at _Ptr
// Only destroy if not in pre-allocated memory.
//if (_Ptr < m_mem || _Ptr >= m_mem+stack_size)
std::_Destroy(_Ptr);}
_SIZTmax_size()const
_THROW0(){
// estimate maximum array size
_SIZT_Count = (_SIZT)(-1) /sizeof
(_Ty);return
(0 <_Count ?_Count : 1);}
};
[9001 byte] By [
Ditlef] at [2008-2-19]
Hi,
Thanks for reporting this issue. The cause is that several assumptions in your allocator conflict with the guarantees of the Standard and changes made in Orcas.
First: Your allocator assumes that it will be used to allocate only types with default constructors. (When m_mem is constructed, each element is default constructed.) The Standard requires allocators to be able to allocate any types: 20.1.5/2 says "Table 31 describes the requirements on types manipulated through allocators", and Table 31 says "T: any type" and "X: an Allocator class for type T". No other requirements are imposed on T.
This matters even when you're using an allocator in a container of an element type that does have a default constructor, because the container can rebind the allocator to allocate internal data structures, some of which may not have default constructors. (A list or map will have to allocate nodes, etc.)
In this case, a conformance fix in Orcas made every Standard container dynamically allocate a helper object (_Aux_cont), which for safety reasons has no default constructor.
Second: Your allocator assumes that when it is used to allocate an object, the allocator will be persistently held until that object is deallocated. This is not guaranteed to hold by the Standard; 20.1.5/4 says "Implementations of containers described in this International Standard are permitted to assume that their Allocator template parameter meets the following two additional requirements beyond those in Table 32. - All instances of a given allocator type are required to be interchangeable and always compare equal to each other. [...]" The equality comparison, from Table 32, "returns true iff storage allocated from each can be deallocated via the other".
The allocators in VC8 tended to be persistently held (I didn't do an exhaustive search, so there may be exceptions). In Orcas, when an _Aux_cont is allocated, an allocator for it is rebound, used, and immediately destroyed. Later, when the _Aux_cont is deallocated, an allocator for it is rebound, used, and immediately destroyed.
Therefore, even if your allocator handled types without default constructors, there would still be a problem (but it would manifest itself as a crash at runtime, rather than a failure at compiletime).
Third: Your allocator contains things inside itself, so assignment isn't possible without changing the addresses of allocated objects. (Your implementation makes assignment a no-op.) Even if your allocator handled types without default constructors, and Orcas persistently held its _Aux_cont allocator, there would still be a problem: the memory that the _Aux_cont lives in must be "tear-offable". When two vectors (each owning an _Aux_cont) are swapped, ownership of the aux objects is swapped. That is, vector A now owns the aux object that B created, and B owns the aux object that A created. If A is destroyed, it needs to be able to deallocate the aux object that B originally created. This cannot work because - even if the _Aux_cont allocator were persistently held - it would be contained within B.
Fourth: Vector swaps can't even compile because your allocator does not support equality comparisons (if they did, the third point would result in a runtime crash).
Fifth: Your allocator does not remember when it has handed out its internal pointer, so if short allocations are repeatedly requested, it will hand out its internal pointer again and again.
To make your allocator work with Orcas, you should give it the ability to handle types without default constructors (#1). It should also satisfy 20.1.5/4, working even when not persistently maintained in memory (#2). To make it work with swaps, the memory it returns must not be part of the allocator itself (#3), and an equality comparison should be implemented (#4). It should always return unique pointers (#5). For example, I believe that this would work:
Implement a CustomAllocator<A, B, B *, N, bool *>. A is the type being allocated. B is the element type of the container (e.g. int), and the third template argument is a pointer to a pool of N B's. The fifth template argument is a pointer to a bool that is used to remember whether the pool of B's has been handed out. When A is different from B (e.g. A is _Aux_cont), CustomAllocator performs ordinary dynamic memory allocation. When A is the same as B, the request is <= N, and the pool has not yet been handed out, the pool pointer is returned and the bool is set. Otherwise (large requests, or the pool is already in use), dynamic memory allocation is performed. Deallocation is simple: pointers not equal to the pool are deleted, and pointers equal to the pool will unset the bool.
This satisfies 20.1.5/4 because the allocator is stateless.
In release mode, disabling _SECURE_SCL in Orcas will make it behave like VC8 (no aux object). However, the aux object is always allocated in debug mode, even when both _HAS_ITERATOR_DEBUGGING and _SECURE_SCL are disabled (this is not strictly necessary, but a consequence of the present structure of the code).
I'll investigate whether we can persistently hold the _Aux_cont allocator (as the other allocators are held), and whether Debug mode with _HAS_ITERATOR_DEBUGGING and _SECURE_SCL disabled can avoid all of this aux machinery. However, the other issues (#1, #3, #4, #5) remain real issues with the allocator.
If space is at a premium, you may wish to write a custom container.
If you have any further questions, feel free to E-mail me. Thanks!
Stephan T. Lavavej (stl@microsoft.com)
Visual C++ Libraries Developer
Thank you for a lot of in-depth information.
Should have expected that STL is a wider minefield than handled by my code.
Had another look at the code yesterday.
Think internal memory should be just array of bytes.
Should fix problem with only default constructors and always constructing N objects.
But this raises other problems like proper destruction, assignment etc, f.eks. clear() won't destroy all.
I left out the compare operators. These need checking of identical assignment operators.
Got some code 'working' using this approach, but I'll go back and take a hard look at this optimalization;
- is it really worth the extra complexity and possible time-bombs for 'improper' use? Instead of KISS.
Thank you
Ditlef