Different implementations of begin() and end() of a container

Viewed 132

I am practicing implementing containers. My goal is to define the iterators begin() and end() so that I can have loops in the form of for(auto x : v). My container looks like this:

class Vector{
public:
    Vector(initializer_list<double> numbers){
        sz = numbers.size();
        elem = new double[sz];
        int i = 0;
        for (auto it = numbers.begin(); it!=numbers.end(); ++it)
            elem[i++] = *it;
    }
    ~Vector(){delete [] elem;}
    double* begin();
    double* end();
private:
    double* elem;
    int sz;
    
};

Option 1

This is how I have defined the iterators (and they work perfectly fine in my test cases)

double* Vector::begin(){
    return elem;
}

double* Vector::end(){
    return &elem[sz];
}

Option 2

This is how they are defined in A Tour of C++

double* Vector::begin(){
    return &elem[0];
}

double* Vector::end(){
    return &elem[0]+sz;
}

My question

As far as I can see both options work fine (assuming the container is non-empty). Does Option 2 have any advantages compared to Option 1 (and vice versa)? I appreciate any suggestions.

2 Answers

While &elem[sz] and &elem[0]+sz will wind up giving you the same result on most/all systems, the first is actually undefined behavior. When you do

&elem[sz]

you are actually doing

&*(elem +sz)

and that *, the dereference, is to an element that doesn't exist. That is undefined behavior per the C++ standard.

With

&elem[0]+sz

you get a pointer to the first element which is legal provided the pointer points to an actual array, and then you advance it to be one past the end. This is a legal and a correct way to get the end iterator provided elem is not null and points to a valid array.


Another way to do this is to just use

return elem + sz;

as it doesn't require any dereferencing.

Both of these options work, and I would be astonished if the compiler didn't generate equivalent code for each version.

I actually prefer your implementation of begin, since if elem is a pointer, then &elem[0] is redundant compared to elem.

Another option: for end, you could do something like

return begin() + size();

assuming that you have a size() member function. This doesn't require any addresses to be taken and more directly says "the position that's size() steps down from where begin() points." But that's just my opinion. :-)

Hope this helps!

Related