2

I want to understand how std::vector<T>::push_back and std::vector<T>::pop_back create and destroy objects in an allocated memory ?

I used google and all I find is people just play with size and capacity to limit the access to the internal dynamic array but I don't think that's how things really work in the standard implementation

Note: I'm not asking for the standard implementation because it would be complex but I would appreciate a basic implementation for this approach


EDIT: I figured out how to implement my own custom allocator

For simplicity I'll show only important functions out of my custom allocator

template <typename T>
T* allocate(std::size_t count) {
    return static_cast<T*>(::operator new(count * sizeof(T)));
}

template <typename T>
void deallocate(T* ptr, std::size_t count) {
    operator delete(ptr);
}


template <typename U, typename... Args>
void construct(U* ptr, Args&&... args) {
    new(ptr) U(std::forward<Args>(args)...);
}

template <typename U>
void destroy(U* ptr) {
    ptr->~U();
}

then I use then in my own defined vector something like this

int* buff = allocate<int>(8);
// This is like:
// std::vector<int> vec;
// vec.reserve(8);

int* last = &buff[0];
construct<int>(last, 32);
// This is like:
// vec.push_back(32);

++last;
construct<int>(last, 12);
// This is another push
// vec.push_back(12);

destroy(last);
--last;
// This is like: 
// vec.pop_back();


deallocate(buff, 8);
// This shoud be in:
// ~vector();

Please check it out if missed something ... thanks

Laith
  • 1,248
  • 2
  • 11
  • 19
  • 2
    Look for placement new and placement delete. – Matteo Italia Jun 06 '16 at 17:54
  • Surely there are plenty of implementations of a vector available online by people whose intent was a learning exercise and not a robust and professional implementation. – chris Jun 06 '16 at 17:54
  • 3
    The basic idea is that `std::vector` holds a pointer to an array `T[]`. It also keeps track of the number of elements in the array. If `.push_back` exceeds the limit then new, bigger array is allocated and the old one is destroyed. So, yes, that's pretty much how this works. – freakish Jun 06 '16 at 17:55
  • 2
    @chris unless that implementation is available here on StackOverflow, there's no reason the question shouldn't be answered here. – Mark Ransom Jun 06 '16 at 17:57
  • 1
    Combine the comments by freakish and MatteoItalia and you get close to how it works. It keeps track of a contiguous block of memory, the size of which defines `capacity`. If you want to then `push_back` an item, it uses placement new to construct the object in-place, in this block of memory, and it increments `size`. If `size` ever needs to exceed `capacity`, it will create a new, larger block of memory, copy all the elements from the previous block into the new larger block, and then call placement new to construct the new object. There is no concept of placement delete, just placement new – Steve Lorimer Jun 06 '16 at 18:09
  • @MatteoItalia only placement new - there is no placement delete – Steve Lorimer Jun 06 '16 at 18:09
  • you can look at the implementations of various vendors if you are curious. for example, gcc puts headers in `/usr/include/c++/` (though they may look confusing to new learners) – kmdreko Jun 06 '16 at 18:10
  • @SteveLorimer: `ptr->~ClassName()`; call it as you prefer. – Matteo Italia Jun 07 '16 at 00:49
  • Would you guys review my implementation ... I think I figured it out – Laith Jun 09 '16 at 17:41

1 Answers1

4

All standard containers with an allocator are using the allocator to construct or destroy elements:

23.2.1 [3] General container requirements (N4296)

For the components affected by this subclause that declare an allocator_type, objects stored in these components shall be constructed using the allocator_traits::construct function and destroyed using the allocator_traits::destroy function

The default allocator in the standard library is using placement new to construct and invokes the destructor to destroy an element:

20.7.9 [11] and [12] The default allocator (N4296)

template <class U, class... Args>
void construct(U* p, Args&&... args);

Effects: ::new((void *)p) U(std::forward(args)...)

template <class U>
void destroy(U* p);

Effects: p-> ~ U()