简体   繁体   中英

Implementation of back() in std::vector

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 a T* (which is still a valid implementation although one which has become unfashionable for various reason). Contemporary std::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?

从您的问题_Myfirst + size()我想你正在使用具有iterator调试的MSVC ,你确定你正在使用release版本进行测试,因为在debug构建iterator调试需要一些额外的时间,但在release模式下他们都采取相等的时间(至少在我使用MSVC 2010机器上)

Because you only get the value and do not use it, the compiler will optimize these codes to no-ops(with -O2).

So the timming is not accurate for comparing the two operation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM