Why is the Vector back() method implemented in terms of iterators
reference back()
{ // return last element of mutable sequence
return (*(end() - 1));
}
rather than something like…
return (*(_Myfirst + size()));
Background to this question:
I have been working recently on optimising some legacy code (an allocator implementation) and noticed that a significant amount of time was being spent in std::vector::back(). So I did some experiments using different collections (Vector vs List vs Deque) and since back() is basically retrieving the last element in the collection so I also compared vector::back() against vector[size()-1]
This is the code I used to test:
#include <vector>
#include <list>
#include <deque>
#include <algorithm>
#include <boost/timer/timer.hpp>
int RandomNumber () { return (rand()%100); }
void doVector( std::vector<int>& test_vec )
{
std::cout << "vect back() = " << test_vec.back() << std::endl;
{
boost::timer::auto_cpu_timer t;
for (int i = 0; i < 100000000; i++ )
test_vec.back();
}
}
void doVector2( std::vector<int>& test_vec )
{
std::cout << "vect [size()-1] = " << test_vec[test_vec.size()-1] << std::endl;
{
boost::timer::auto_cpu_timer t;
for (int i = 0; i < 100000000; i++ )
test_vec[test_vec.size()-1];
}
}
void doList( std::vector<int>& test_vec )
{
std::list<int> test_list(test_vec.begin(),test_vec.end());
std::cout << "list back() = " << test_list.back() << std::endl;
{
boost::timer::auto_cpu_timer t;
for (int i = 0; i < 100000000; i++ )
test_list.back();
}
}
void doDeque( std::vector<int>& test_vec )
{
std::deque<int> test_deq(test_vec.begin(),test_vec.end());
std::cout << "Deque back() = " << test_deq.back() << std::endl;
{
boost::timer::auto_cpu_timer t;
for (int i = 0; i < 100000000; i++ )
test_deq.back();
}
}
int _tmain(int argc, _TCHAR* argv[])
{
std::vector<int> test_vec(100);
std::generate(test_vec.begin(), test_vec.end(), RandomNumber );
doVector(test_vec);
doVector2(test_vec);
doList(test_vec);
doDeque(test_vec);
}
The results were :
Removed Results because I had turned on Debug / turned off Optimisations in release times in the order of seconds – I should have seen that
Clearly I will gain significantly from using vect[ size() – 1] and further to that I will also gain by using vect[0] over front(). I realise that I am tying myself to vector but in the short term that’s my chosen direction (Quick win). But, it made me think – why is it that the vector implementations of front() and back() use a less efficient implementation under the hood?
vect back() = 41
0.183862s wall, 0.171601s user + 0.000000s system = 0.171601s CPU (93.3%)
vect [size()-1] = 41
0.416969s wall, 0.421203s user + 0.000000s system = 0.421203s CPU (101.0%)
list back() = 41
0.079119s wall, 0.078001s user + 0.000000s system = 0.078001s CPU (98.6%)
Deque back() = 41
0.186574s wall, 0.187201s user + 0.000000s system = 0.187201s CPU (100.3%)
Clearly I was looking at the wrong results when I did my analysis – as this totally backs up the choice of the implementation. Apologies to people who looked at this before this edit…..
The C++ specification defines semantics and not implementation. If it matters on an implementation that
std::vector<T, A>::back()is implemented using a different implementation than the specification suggests, an implementation should just do the Right Thing! Obviously, it needs to still deliver the correct semantics.The basic reason it is specified as is probably comes from the original implementation: The iterator type of
std::vector<T, A>was just aT*(which is still a valid implementation although one which has become unfashionable for various reason). Contemporarystd::vector<T, A>implementations tend to use a simple wrapper for pointers.All that said, the overhead you see should actually be optimized because for decent compilers: What optimization flags did you use?