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

typedefvalue_type*pointer;

typedefvalue_type&reference;

typedefconstvalue_type*const_pointer;

typedefconstvalue_type&const_reference;

typedefsize_tsize_type;

typedefptrdiff_tdifference_type;

template<class_Other>

structrebind

{// convert an allocator<_Ty> to an allocator <_Other>

typedefScVectorStackAllocator<_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(constScVectorStackAllocator&)_THROW0()

{// construct by copying (do nothing)

}

template<class_Other/*, size_t otherSize*/>

ScVectorStackAllocator(constScVectorStackAllocator<_Other,stack_size>&)_THROW0()

{// construct from a related allocator (do nothing)

}

template<class_Other/*, size_t otherSize*/>

ScVectorStackAllocator<_Ty,stack_size>&operator=(constScVectorStackAllocator<_Other,stack_size>&)

{// assign from a related allocator (do nothing)

return *this;

}

voiddeallocate(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));

}

voidconstruct(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);

}

voiddestroy(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]
# 1

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

StephanT.Lavavej-MSFT at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Visual C++ Orcas...
# 2

I should add that such a pool allocator would require a different pool for each vector (otherwise, it would just use dynamic allocation all the time). This is clearly not desirable if you have a vector of vectors.

You could write an Allocator<A, B, N> (where A is the type being allocated, B is the container element type, and N is the stack size) when contains nothing when A != B and falls back to dynamic allocation, and contains an array when A == B and behaves like your allocator currently does. Although this would not satisfy 20.1.5/4, it would avoid stepping on the _Aux_cont machinery, and make your code work in Orcas as it did in VC8. You would have to fix #1 if you wanted to handle elements without default constructors, and #5 may already be causing trouble. You wouldn't ever be able to swap such vectors (#3, #4). But it should compile and work like it used to.

Stephan

StephanT.Lavavej-MSFT at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Visual C++ Orcas...
# 3

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

Ditlef at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Visual C++ Orcas...
# 4

You're welcome!

> Should have expected that STL is a wider minefield than handled by my code.

Changing how STL containers allocate memory is a very deep change, and attempting to do anything unusual should be done with great care.

> is it really worth the extra complexity and possible time-bombs for 'improper' use?

My guess is that it almost certainly is not. First, profiling would have to indicate that small vector allocations are actually a performance bottleneck. Even if they were, a much more targeted change (perhaps avoiding the small vectors in the first place) could be simpler and easier. The allocator approach has grave correctness considerations, and also performance costs of its own: now vectors are larger, perhaps consuming unnecessary space.

If profiling identified a bottleneck, and the structure of the code could not be changed, then the best solution might be a custom vector-like data structure. The problem is with trying to make std::vector store elements on the stack (something that it is not designed to support), not with the idea of such a data structure by itself.

Thanks,

Stephan T. Lavavej

StephanT.Lavavej-MSFT at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Visual C++ Orcas...

Visual Studio Orcas

Site Classified