Are const_iterators faster?
If nothing else, a const_iterator
reads better, since it tells anyone reading the code "I'm just iterating over this container, not messing with the objects contained".
That's a great big win, never mind any performance differences.
Should I prefer iterators over const_iterators?
I totally agree with you.
I think the answer is simple:
Use const_iterators where const values are the right thing to use, and vice versa.
Seems to me that those who are against const_iterators must be against const in general...
Different efficiency of iterator and const_iterator (STL)
The documentation says that these const_iterators should be used whenever possible since they are faster.
It sure does. From http://qt-project.org/doc/qt-4.8/containers.html#stl-style-iterators:
For each container class, there are two STL-style iterator types: one that provides read-only access and one that provides read-write access. Read-only iterators should be used wherever possible because they are faster than read-write iterators.
What a stupid thing to say.
Safer? Yes. Faster? Even if this were the case (it apparently isn't with gcc and clang), it is rarely a reason to prefer const iterators over non-const ones. This is premature optimization. The reason to prefer const iterators over non-const ones is safety. If you don't need the pointed-to contents to be modified, use a const iterator. Think of what some maintenance programmer will do to your code.
As far as begin
versus cbegin
is concerned, that's a C++11 addition. This allows the auto
keyword to use a const iterator, even in a non-const setting.
How does using const_iterator result in the compilation of a more efficient program?
const_iterator
doesn't exist to make programs faster, it exists for type-safety. If you want to allow code to iterate over your container but you want to be sure it won't modify the contents, you can give that code const_iterator
s.
Why would you need separate data structures for iterating through a container that is constant throughout a program, as opposed to iterating through a container that is non-constant?
You don't necessarily need const_iterator
to just iterate over a container, but having separate iterators types allows you to express your intentions better. If you want to iterate without changing anything, using a const_iterator
makes that intention explicit.
If I have an array of int
I could pass int*
to functions that want to access it, but if I want to ensure the functions don't modify the array elements I would pass const int*
instead. And if I have an array of const int
then I must pass const int*
because I can't get a non-const int*
to the array without using a questionable (and possibly dangerous) cast.
You can use a const_iterator
(or its equivalent) to perform non-modifying traversals of const or non-const objects. You can only use a mutable iterator
to traverse non-const objects, to prevent you attempting to modify elements of a const container.
Related question: When creating classes in C++, is it possible to define what happens when an instance of the class is declared const?
No, the same constructor is called whether the object is declared const or not, and the type of *this
is always non-const during the constructor.
What is the difference between const_iterator and non-const iterator in the C++ STL?
const_iterator
s don't allow you to change the values that they point to, regular iterator
s do.
As with all things in C++, always prefer const
, unless there's a good reason to use regular iterators (i.e. you want to use the fact that they're not const
to change the pointed-to value).
Why does Scott Meyers recommend to prefer `iterator` to `const_iterator`
The short answer is that Meyers does consider const_iterator
s preferable when using an up-to-date implementation of the STL due to improvements made in the C++11 standard.
Before discussing his reasons for giving the anti-const_iterator
advice in the first place and explaining what changed, though, I need to clear up a misconception. You write:
isn't that the whole point of a
const_iterator
, that it does not allow modifying the container?
This is a sensible assumption, but in fact this is not quite the purpose of const_iterator
. As Meyers explains in the third edition of Effective C++ (written, notably, before C++11):
Declaring an
iterator
const
is like declaring a pointerconst
(i.e., declaring aT* const
pointer): theiterator
isn't allowed to point to something different, but the thing it points to may be modified. If you want an iterator that points to something that can't be modified (i.e., the STL analogue of aconst T*
pointer), you want aconst_iterator
[.]
In short, const_iterator
doesn't protect against modifying the container, it protects against modifying the contained values. This is why Meyers expects insert
to be compatible with const_iterator
: it doesn't modify any of the elements already present in the container.
erase
is a bit stranger, because it causes a contained element to be destroyed, which is a non-const
operation. But note that the element's destructor is not called via the iterator itself; the iterator is merely the means the API provides to specify the item to be erase
d. Semantically, a const_iterator
should be able to serve this purpose as well as an an iterator
.
Now, as for the advice in Effective STL and its subsequent retraction, I'll paraphrase and quote some of Effective Modern C++ on the matter. In Item 13, "Prefer const_iterator
s to iterator
s", Meyers writes:
...in C++98,
const_iterators
had only halfhearted support. It wasn't that easy to create them, and once you had one, the ways you could use it were limited.......there was no simple way to get a
const_iterator
from a non-const
container...Once you had the
const_iterator
s...locations for insertions (and erasures) could be specified only byiterator
s.const_iterator
s weren't acceptable.
He gives an example making extensive use of static_cast
to get around these limitations, but points out,
...the code I've shown might not compile, either, because there's no portable conversion from a
const_iterator
to aniterator
, not even with astatic_cast
. Even the semantic sledgehammer known asreinterpret_cast
can't do the job.
He summarizes:
...
const_iterator
s were so much trouble in C++98, they were rarely worth the bother.
These issues were addressed by the C++11 standard. As alluded to in a comment on your question, this standard introduced cbegin
and cend
, which return const_iterator
regardless of whether the container itself is const
. Also, insert
and erase
were given overloads taking const_iterator
. This makes const_iterator
much easier to use.
Speed accessing a std::vector by iterator vs by operator[]/index?
The performance difference is likely negligable or none (the compiler might optimise them to be identical); you should worry about other things, like whether your program is correct (a slow but correct program is better than a fast and incorrect program). There are other advantages to using iterators though, such as being able to change the underlying container to one with no operator[]
without modifying your loops. See this question for more.
const_iterators will most likely have none, or negligable, performance difference compared to ordinary iterators. They are designed to improve the correctness of your program by preventing modifying things that shouldn't be modified, not for performance. The same goes for the const
keyword in general.
In short, optimisation should not be a concern of yours until two things have happened: 1) you have noticed it runs too slowly and 2) you have profiled the bottlenecks. For 1), if it ran ten times slower than it could, but is only ever run once and takes 0.1ms, who cares? For 2), make sure it's definitely the bottleneck, otherwise optimising it will have nearly no measurable effect on performance!
How to remove constness of const_iterator?
There is a solution with constant time complexity in C++11: for any sequence, associative, or unordered associative container (including all of the Standard Library containers), you can call the range-erase member function with an empty range:
template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
return c.erase(it, it);
}
The range-erase member functions have a pair of const_iterator
parameters, but they return an iterator
. Because an empty range is provided, the call to erase does not change the contents of the container.
Hat tip to Howard Hinnant and Jon Kalb for this trick.
How const_iterators are implemented?
How the const_iterators are implemented for example in the STL containers?
You can probably look in your implementation's header files to see how they chose to do it. It will depend on the container type and its internal data structure. But the common pattern I've seen is that there's some private template that can take either non-const T
or const T
, and that will be used as a base or member of the actual distinct iterator types. One catch is that a specific type from the data structure needs to be used, independently of the type the iterator classes expose. In your example, Node<T>
and Node<const T>
are not interoperable, so SomeComonFunctionsBetweenIterators
could work, but should probably have the node type as a template argument independent of the value type T
.
But for an easy way to define iterator types, I always turn to Boost.Iterator's iterator_facade
or iterator_adaptor
. They take care of a lot of the technical requirements about iterators, letting the author just fill in the behavioral bits. iterator_facade
is for implementing something "from scratch", and iterator_adaptor
is for the common case of an iterator that contains and wraps another iterator.
The Boost documentation for iterator_facade
discusses a way to define related iterator
and const_iterator
types. (Though their decision to make the iterators interoperable as long as the pointers can convert probably wouldn't be appropriate for iterators from containers.)
Using iterator_facade
will also give a bunch of things algorithms may expect of iterators but missing from your code shown, like making std::iterator_traits
work, equality and inequality, and other fiddly details.
#include <boost/iterator/iterator_facade.hpp>
#include <type_traits>
template <typename T>
struct Node
{
T value;
Node* next;
};
template <typename NodeType, typename ValueType, typename ContainerType>
class TLinkedListIterator :
public boost::iterator_facade<
TLinkedListIterator<NodeType, ValueType, ContainerType>, // CRTP Derived type
ValueType, // Iterator's value_type
std::forward_iterator_tag // Category
>
{
public:
TLinkedListIterator() : m_node(nullptr) {}
protected:
explicit TLinkedListIterator(NodeType* node) : m_node(node) {}
private:
friend boost::iterator_core_access;
friend ContainerType;
template <typename N, typename V, typename C> friend class TLinkedListIterator;
ValueType& dereference() const { return m_node->value; }
void increment() { m_node = m_node->next; }
// Templating allows comparison between iterator and const_iterator
// in any combination.
template <typename OtherValueType>
bool equal(const TLinkedListIterator<NodeType, OtherValueType, ContainerType>& iter)
{ return m_node == iter.m_node; }
NodeType m_node;
};
template <typename T>
class LinkedList
{
public:
using iterator = TLinkedListIterator<Node<T>, T, LinkedList>;
class const_iterator
: public TLinkedListIterator<Node<T>, const T, LinkedList>
{
private:
using BaseType = TLinkedListIterator<Node<T>, const T, LinkedList>;
public:
const_iterator() = default;
// Converting constructor for implicit conversion from iterator
// to const_iterator:
const_iterator(const iterator& iter)
: BaseType(iter.m_node) {}
protected:
explicit const_iterator(Node<T>* node)
: BaseType(node) {}
friend LinkedList<T>;
};
iterator begin() { return iterator(get_head()); }
iterator end() { return iterator(); }
const_iterator begin() const { return const_iterator(get_head()); }
const_iterator end() const { return const_iterator(); }
const_iterator cbegin() const { return begin(); }
const_iterator cend() const { return end(); }
// ...
};
Related Topics
Subtle C++ Inheritance Error with Protected Fields
Is Sizeof(*Ptr) Undefined Behavior When Pointing to Invalid Memory
Do I Need to Close a Std::Fstream
Boolean Values as 8 Bit in Compilers. Are Operations on Them Inefficient
Avoiding Denormal Values in C++
How to Copy/Paste from the Clipboard in C++
Defining a Variable in the Condition Part of an If-Statement
Should I Use Shared_Ptr or Unique_Ptr
Checking Value Exist in a Std::Map - C++
Is There Any Standard Way of Embedding Resources into Linux Executable Image
With a Private Modifier, Why Can the Member in Other Objects Be Accessed Directly
Round Double to 3 Points Decimal
Function Composition in C++/C++11
Why Was the Space Character Not Chosen for C++14 Digit Separators
Why Are C++ Stl iOStreams Not "Exception Friendly"
Cross Platform Format String for Variables of Type Size_T
Why Can't I Create a Vector of Lambdas (Of the Same Type) in C++11