The below code gives an output of:

Allocated!
Allocated!
Deleted!
Allocated!
Allocated!
Deleted!
Size: 3
Capacity: 4
vector[0]: isAligned!
20
30
40
Deleted!
Deleted!

Can someone explain the first allocation and why there are 4 allocations instead of 2 (one for each reserve?). I figure the first is because the vector stores some memory before when created but why there are two after the first reserve instead of one baffles me. (Even without the second reserve its there just was testing to make sure all the copying and stuff worked fine since this is my first time using an allocator in a template)

//Custom Allocator
#include <memory>
#include <allocators>
#include <iostream>

template<typename T>
class Allocator16
{
public:
	typedef T value_type;
	typedef value_type* pointer;
	typedef const value_type* const_pointer;
	typedef value_type& reference;
	typedef const value_type& const_reference;
	typedef std::size_t size_type;
	typedef std::ptrdiff_t difference_type;
public:
	template<typename U>
	struct rebind{
		typedef Allocator16<U> other;
	};
public:
	inline Allocator16(){}
	inline ~Allocator16(){}
	inline Allocator16(Allocator16 const&){}
	template<typename U>
	inline explicit Allocator16(Allocator16<U> const&){}

	//address
	inline reference pointer_address(reference r){return &r;}
	inline const_pointer address(const_reference r){return &r;}
	
	//memory allocation
	inline pointer allocate(size_type cnt,typename std::allocator<void>::const_pointer = 0)
	{	std::cout << "Allocated!" << std::endl;
		return (T*)_aligned_malloc(sizeof(T)*cnt,16);}
	inline void deallocate(pointer p,size_type)
	{
		std::cout << "Deleted!" << std::endl;
		_aligned_free(p);
	}

	// size
	inline size_type max_size() const
	{return std::numeric_limits<size_type>::max()/sizeof(T);}

	//construction/destruction
	inline void construct(pointer p,const T& t){new(p) T(t);}
	inline void destroy(pointer p){p->~T();}
	inline bool operator==(Allocator16 const&){return true;}
	inline bool operator!=(Allocator16 const& s){return !operator==(s);}
};
//Main Code
using namespace std;

template<typename T>
class Aligned16Vector : public vector<T,Allocator16<T>>
{
public:
	Aligned16Vector(void)
	{
		vector<T,Allocator16<T>>::vector();
	}
};

bool isAligned(void* p)
{
	if(((unsigned long)p & 15) == 0)
		return true;

	return false;
}

int _tmain(int argc, _TCHAR* argv[])
{
	Aligned16Vector<float> myVector;
	myVector.reserve(4);
	myVector.push_back(20.0f);
	myVector.push_back(30.0f);
	myVector.push_back(40.0f);
	myVector.reserve(10);
	std::cout << "Size: " << myVector.size() << std::endl;
	std::cout << "Capacity: " << myVector.capacity() << std::endl;
	if(isAligned(&myVector[0]))
		std::cout << "vector[0]: isAligned!" << std::endl;
	if(isAligned(&myVector[1]))
		std::cout << "vector[1]: isAligned!" << std::endl;

	for(int i = 0; i < myVector.size();)
	{
		cout << myVector[i++] << endl;
	}

	myVector.~Aligned16Vector();
	std::cout << "Size: " << myVector.size() << std::endl;

	unsigned char c = 0;

	do
	{
		cin >> c;
	}while(c != 'q');

	return 0;
}

Recommended Answers

All 3 Replies

It is quite intriguing indeed. Maybe you should try to also print out the counts on the allocate calls.

So far, I don't know what would cause the double allocate, especially since it doesn't match the number of deallocate calls, that is even more disturbing in my opinion (I say the number of Delete and Allocate doesn't match, but I'm assuming that the code you posted corresponds to the output you got, because the explicit call of the destructor in addition to the automatic destruction of the vector explains the double Delete at the end, but that's an error, it is actually just one delete and then a memory corruption).

k did some more testing and had it print the number of elements. The double delete actually is triggered by the destructor. The Allocate/Delete calls do add up just the order is a bit wonky. Seems it prints a buffer of size 1 to store data by default, but you delete that with the reserve then create a new one. There is another call to of size 1. I think it might be a temporary cache for stuff like switching or copying.

Reserve I think translates to
Allocate new buffer
copy from old to new
Delete old buffer

int _tmain(int argc, _TCHAR* argv[])
{
	Aligned16Vector<float> myVector;
	myVector.reserve(4);
	return 0;
}
Allocated! 1 
Allocated! 1 
Deleted!
Allocated! 4 
Deleted!
Deleted!

Get rid of this:

Aligned16Vector::Aligned16Vector()
{
    vector<T,Allocator16<T>>::vector(); // why?
    // what is the purpose of constructing this anonymous object, 
    // which is immediately destroyed thereafter?
}

And as already mentioned, this: myVector.~Aligned16Vector(); and things would work out fine.

> The Allocate/Delete calls do add up just the order is a bit wonky.
> Seems it prints a buffer of size 1 to store data by default, but you delete that with the reserve
> then create a new one. There is another call to of size 1.
> I think it might be a temporary cache for stuff like switching or copying.

Ah, the joys of Microsoft's checked iterators! Just turn the damn thing off; #define _SECURE_SCL 0 IIRC.

> Reserve I think translates to
> Allocate new buffer
> copy from old to new
> Delete old buffer

It is a little more elaborate - one needs to take advantage of move semantics if possible, and if move is not possible then take care of exceptions which may be thrown by the copy constructor.

A reference implementation of reserve would look like:

template< typename T, typename A > void std::vector<T,A>::reserve( size_type n )
{
    if( n > capacity() )
    {
        pointer temp = A::allocate( n ) ;
        size_type i = 0;
        try
        {
            for( ; i < size() ; ++i )
                 new ( temp + i ) value_type( std::move_if_noexcept( (*this)[i]) ) );
        }
        catch(...)
        {
            while( i > 0 ) temp[--i].~value_type() ;
            deallocate(temp) ;
            throw ;
        }

        // deallocate old buffer ;
        // update variables to refer to new buffer
    }
}

Of course, a real-life implementation should avoid the overhead of the try/catch if move with nothrow is available or the copy constructor is trivial.

A conforming allocator must be able to allocate objects of any type (rebind<>), so to be pedantically correct:

inline pointer allocate( size_type cnt, const_pointer =  nullptr )
{ 
    enum { ALIGNMENT = std::alignment_of<T>::value > 16 ? std::alignment_of<T>::value : 16 } ;      
    return (T*)_aligned_malloc( sizeof(T)*cnt, ALIGNMENT ) ;
}

I suppose that you realize that this fancy alignment of the buffer at a 16-byte boundary is completely meaningless for any T where sizeof(T) is not an integral multiple of 16.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.