Behavior of std::vector::pop_back! Does it really erase?

Apr 12, 2020 at 4:01pm

So I am trying to write an implementation that mimics the std::vector the best I can.
When writing the pop_back function I came across a behavior in std::vector::pop_back that I can't understand.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<double> v;

    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(3);
    v.push_back(345);

    v.pop_back();

    try
    {
        cout << v.capacity()<< endl;

        cout << v.size()<< endl;

        cout << endl << v[24]<< endl; // this line prints the "I thought it was erased" element from the vector
        cout << endl << v.at(24)<< endl; // throws an exception

    }
    catch(exception& e)
    {
        cout << e.what() << endl;
    }

    

}


As I see it the pop_back function doesn't really erase the element, it just decrements the size of the vector, am I right?
Last edited on Apr 12, 2020 at 4:04pm
Apr 12, 2020 at 4:12pm
Yes pop_back() just decreases the size of the vector it doesn't affect the capacity of the vector, nor does it actually clear the affected memory. However trying to read or write outside the bounds of the vector produces undefined behavior.



Apr 16, 2020 at 10:12am
Thanks for the answer
Apr 16, 2020 at 10:51am
To be absolutely clear - as far as the code in the vector object is concerned, that 25th element is no longer there. It has been removed, and is no longer considered to be part of the vector. It can be re-used and overwritten at any time.

The fact that the object may have pre-allocated more than 24 elements worth of space for future use, and that there may still be some data in the memory immediately after the 24th element, is irrelevant.

It's exactly the same situation as when you free any dynamically allocated memory. The same data may or may not still be left in that memory, but the memory manager no longer considers that memory to be yours to use.
Last edited on Apr 16, 2020 at 10:52am
Apr 16, 2020 at 2:16pm
Oh I see, thanks for the clarification, it was a bit of surprise when I found about that.
Apr 16, 2020 at 2:51pm
Er, to be clear, the popped object’s destructor must be called. Integers don’t have destructors.
Apr 21, 2020 at 1:33am
@Duthomhas could you please elaborate on that?
Apr 21, 2020 at 5:29am
std::vector asks its allocator for a block of aligned uninitialized storage for its elements. It calls operator new directly to construct the element in place; when the vector is done with the element it directly calls the element's destructor.

The vector contains some code somewhat like this:
1
2
3
4
5
6
7
8
value_type* end_; // member data: points one past-the-end
// the vector stores elements in the iterator span [begin_, end_).

void pop_back()
{
  --this->end_; 
  this->end_->~value_type(); // destroy the element
}

When value_type is a class type, this is a call to the element's destructor.
When value_type is plain int this is a call to the element's pseudo-destructor. It has no effect prior to C++20; since then it formally ends the lifetime of the int.

This feature exists primarily so that a template can destroy objects of type T without caring whether or not T happens to be a class type.
Last edited on Apr 21, 2020 at 5:33am
Topic archived. No new replies allowed.