const and non-const in stl containers
The STL vector template defines element accessors as both const and non-const variants, for example:
reference operator[](size_type __n)
{return *(this->_M_impl._M_start + __n);}
const_reference operator[](size_type __n) const
{return *(this->_M_impl._M_start + __n);}
When does the compiler decide to use one version over the other? The vector itself is not defined as const, neither are the elements stored in it. So given two functions:
A f(int i) const
{ return myVector[i]; }
A f(int i)
{ return myVector[i]; }
My understanding is that the first would call the const version of operator[] and return a const A. The second calls the non-const version and returns a non-const A?
To 开发者_运维百科me, the first version of f() seems to be the "correct" one to write since the function isn't altering anything, but it is perhaps surprising to the caller that it returns a const A. Surely if I wanted a const A returned, I should need to define f() as:
const A f(int i) const //Note the extra const at the start
{ return myVector[i]; }
Which would tell whoever is writing the caller to expect a const back.
So the extra const appears by magic? And what does the extra const get applied to if I'm using a boost::ptr_vector rather than std::vector? The data? The pointer? both?
This takes advantage of a tricky part of the function overload rules. First off, cv-qualifiers after the close parenthesis of a method prototype are just like cv-qualifiers on arguments, but they apply to the implicit this
argument. In a hypothetical C++ variant where you had to declare this
, the prototypes would be like so:
reference operator[](this_type this, size_type n);
const_reference operator[](const this_type this, size_type n);
Therefore, if the object you are calling the method on is const
, the second overload is a closer match and will be called. If it's not, the first overload will be called. So you get a const_reference
back if you index a const
container, and you get a reference
back if you index a non-const
container. The const_reference
object then enforces the read-only nature of the container it points into.
Sometimes const_reference
is the same as const reference
and sometimes it isn't; for the more complicated containers, a reference
is a class with nontrivial code, and that code has to be different for the read-only variety. The standard consistently uses const_reference
so that implementors have freedom to do that when they need to.
The const
at the end of a function declaration applies only to non-static member functions, and means that *this
is const-qualified.
struct A {
void f(int i) const;
void f(int i);
};
void g(const A& x, A& y) {
x.f(); // calls const version
y.f(); // calls non-const version
}
In STL containers, this sort of overload is used to determine whether the returned reference or iterator can be used to change the container's elements. You cannot change an element through a const
qualified container.
In the function:
A f(int i) const
{ return myVector[i]; }
You are returning a non-const A. Since you are returning by value, the value returned by operator[] (a const reference) is copied into a new A. You are "losing" the const, but it doesn't matter because you have a whole new object. Rather, if your class looks like this:
struct B {
const A& f(int i) const;
A& f(int i);
private:
vector<A> myVector;
};
and you call f() using a const B:
const B b;
const A& a = b.f();
your a will be a const reference.
Returning a const rvalue is effectively meaningless- it's perfectly legal to construct a new value from a const lvalue, which is why the first form is valid. Think about it- if you want to copy from somewhere, it doesn't matter if you can't write to that somewhere.
If you returned a reference, then the compiler would not allow you to return an A& in the const version.
精彩评论