I've never implemented STL-like iterators and I try to understand how to implement a very basic thing based on pointers. Once I will have this class I will be able to modify it to do more complicated things. Consequently, this is a first step, and I need it to be rock solid to understand how to write my own iterators (without boost).
I have written the following code and I know that there are errors in it. Can you help me to design correctly a Random Access Iterator class inspired from that :
template<Type> class Container<Type>::Iterator : public std::iterator<random_access_iterator_tag, Type>
{
// Lifecycle:
public:
Iterator() : _ptr(nullptr) {;}
Iterator(Type* rhs) : _ptr(rhs) {;}
Iterator(const Iterator &rhs) : _ptr(rhs._ptr) {;}
// Operators : misc
public:
inline Iterator& operator=(Type* rhs) {_ptr = rhs; return *this;}
inline Iterator& operator=(const Iterator &rhs) {_ptr = rhs._ptr; return *this;}
inline Iterator& operator+=(const int& rhs) {_ptr += rhs; return *this;}
inline Iterator& operator-=(const int& rhs) {_ptr -= rhs; return *this;}
inline Type& operator*() {return *_ptr;}
inline Type* operator->() {return _ptr;}
inline Type& operator[](const int& rhs) {return _ptr[rhs];}
// Operators : arithmetic
public:
inline Iterator& operator++() {++_ptr; return *this;}
inline Iterator& operator--() {--_ptr; return *this;}
inline Iterator& operator++(int) {Iterator tmp(*this); ++_ptr; return tmp;}
inline Iterator& operator--(int) {Iterator tmp(*this); --_ptr; return tmp;}
inline Iterator operator+(const Iterator& rhs) {return Iterator(_ptr+rhs.ptr);}
inline Iterator operator-(const Iterator& rhs) {return Iterator(_ptr-rhs.ptr);}
inline Iterator operator+(const int& rhs) {return Iterator(_ptr+rhs);}
inline Iterator operator-(const int& rhs) {return Iterator(_ptr-rhs);}
friend inline Iterator operator+(const int& lhs, const Iterator& rhs) {return Iterator(lhs+_ptr);}
friend inline Iterator operator-(const int& lhs, const Iterator& rhs) {return Iterator(lhs-_ptr);}
// Operators : comparison
public:
inline bool operator==(const Iterator& rhs) {return _ptr == rhs._ptr;}
inline bool operator!=(const Iterator& rhs) {return _ptr != rhs._ptr;}
inline bool operator>(const Iterator& rhs) {return _ptr > rhs._ptr;}
inline bool operator<(const Iterator& rhs) {return _ptr < rhs._ptr;}
inline bool operator>=(const Iterator& rhs) {return _ptr >= rhs._ptr;}
inline bool operator<=(const Iterator& rhs) {return _ptr <= rhs._ptr;}
// Data members
protected:
Type* _ptr;
};
Thank you very much.
Your code has the following issues:
You do not follow the Rule of Three/Five. The best option in your situation is not to declare any custom destructors, copy/move constructors or copy/move assignment operators. Let's follow so called Rule of Zero.
Iterator(Type* rhs) could be private and the Container could be marked as Iterator's friend, but that's not strictly necessary.
operator=(Type* rhs) is a bad idea. That's not what type safety is about.
Post-in(de)crementation should return Iterator, not Iterator &.
Adding two iterators has no meaning.
Subtracting two iterators should return a difference, not a new iterator.
You should use std::iterator<std::random_access_iterator_tag, Type>::difference_type instead of const int &.
If a method does not modify an object, it should be marked const.
Useful resource: RandomAccessIterator # cppreference.com
Here is a fixed version of your code:
template<typename Type>
class Container<Type>::Iterator : public std::iterator<std::random_access_iterator_tag, Type>
{
public:
using difference_type = typename std::iterator<std::random_access_iterator_tag, Type>::difference_type;
Iterator() : _ptr(nullptr) {}
Iterator(Type* rhs) : _ptr(rhs) {}
Iterator(const Iterator &rhs) : _ptr(rhs._ptr) {}
/* inline Iterator& operator=(Type* rhs) {_ptr = rhs; return *this;} */
/* inline Iterator& operator=(const Iterator &rhs) {_ptr = rhs._ptr; return *this;} */
inline Iterator& operator+=(difference_type rhs) {_ptr += rhs; return *this;}
inline Iterator& operator-=(difference_type rhs) {_ptr -= rhs; return *this;}
inline Type& operator*() const {return *_ptr;}
inline Type* operator->() const {return _ptr;}
inline Type& operator[](difference_type rhs) const {return _ptr[rhs];}
inline Iterator& operator++() {++_ptr; return *this;}
inline Iterator& operator--() {--_ptr; return *this;}
inline Iterator operator++(int) const {Iterator tmp(*this); ++_ptr; return tmp;}
inline Iterator operator--(int) const {Iterator tmp(*this); --_ptr; return tmp;}
/* inline Iterator operator+(const Iterator& rhs) {return Iterator(_ptr+rhs.ptr);} */
inline difference_type operator-(const Iterator& rhs) const {return _ptr-rhs.ptr;}
inline Iterator operator+(difference_type rhs) const {return Iterator(_ptr+rhs);}
inline Iterator operator-(difference_type rhs) const {return Iterator(_ptr-rhs);}
friend inline Iterator operator+(difference_type lhs, const Iterator& rhs) {return Iterator(lhs+rhs._ptr);}
friend inline Iterator operator-(difference_type lhs, const Iterator& rhs) {return Iterator(lhs-rhs._ptr);}
inline bool operator==(const Iterator& rhs) const {return _ptr == rhs._ptr;}
inline bool operator!=(const Iterator& rhs) const {return _ptr != rhs._ptr;}
inline bool operator>(const Iterator& rhs) const {return _ptr > rhs._ptr;}
inline bool operator<(const Iterator& rhs) const {return _ptr < rhs._ptr;}
inline bool operator>=(const Iterator& rhs) const {return _ptr >= rhs._ptr;}
inline bool operator<=(const Iterator& rhs) const {return _ptr <= rhs._ptr;}
private:
Type* _ptr;
};
In general your approach is right. The postfix increment/decrement operator should return by value, not by reference. I also have doubts about:
Iterator(Type* rhs) : _ptr(rhs) {;}
This tells everyone that this iterator class is implemented around pointers. I would try making this method only callable by the container. Same for assignment to a pointer.
Adding two iterators makes no sense to me (I would leave "iterator+int"). Substracting two iterators pointing to the same container might make some sense.
Have a look at how Boost do it, the iterators in boost/container/vector.hpp - vector_const_iterator and vector_iterator are reasonably easy to understand pointer based iterators.
Related
I'm trying to understand the C++ standard library. If I want to build a new kind of container fully compatible with latest C++17 standards (excluding polymorphic allocator support as I'm not that clear on that yet), with full compatibility with iterators, what is the complete set of member functions and types I must provide?
Take a sequence container like std::vector for example. In other words, say I want to provide everything that std::vector provides. Also considering allocator support.
Of course I also want to provide iterators, exactly as std::vector does it (I don't want to delegate to it; I want from the ground up remake), so I suppose I will need two nested iterator classes, one for iterator and one for const_iterator.
I've come up with the following:
template <class T, class Alloc = std::allocator_traits<T>>
class StdLibClass
{
public:
using allocator_type = Alloc;
using value_type = typename Alloc::value_type;
using reference = typename Alloc::reference;
using const_reference = typename Alloc::const_reference;
using difference_type = typename Alloc::difference_type;
using size_type = typename Alloc::size_type;
class Iterator
{
public:
using difference_type = typename Alloc::difference_type;
using value_type = typename Alloc::value_type;
using reference = typename Alloc::reference;
using pointer = typename Alloc::pointer;
using iterator_category = std::random_access_iterator_tag; // or another Iterator tag you want
Iterator();
Iterator(const Iterator&);
~Iterator();
Iterator& operator=(const Iterator&);
bool operator==(const Iterator&) const;
bool operator!=(const Iterator&) const;
bool operator<(const Iterator&) const;
bool operator>(const Iterator&) const;
bool operator<=(const Iterator&) const;
bool operator>=(const Iterator&) const;
Iterator &operator++();
Iterator operator++(int);
Iterator &operator--();
Iterator operator--(int);
Iterator &operator+=(size_type);
Iterator operator+(size_type) const;
friend Iterator operator+(size_type, const Iterator&);
Iterator &operator-=(size_type);
Iterator operator-(size_type) const;
difference_type operator-(Iterator) const;
reference operator*() const;
pointer operator->() const;
reference operator[](size_type) const;
};
class ConstIterator
{
public:
using difference_type = typename Alloc::difference_type;
using value_type = typename Alloc::value_type;
using reference = typename const Alloc::reference;
using pointer = typename const Alloc::pointer;
using iterator_category = std::random_access_iterator_tag; // or another Iterator tag you want
ConstIterator();
ConstIterator(const ConstIterator&);
ConstIterator(const Iterator&);
~ConstIterator();
ConstIterator& operator=(const ConstIterator&);
bool operator==(const ConstIterator&) const;
bool operator!=(const ConstIterator&) const;
bool operator<(const ConstIterator&) const;
bool operator>(const ConstIterator&) const;
bool operator<=(const ConstIterator&) const;
bool operator>=(const ConstIterator&) const;
ConstIterator &operator++();
ConstIterator operator++(int);
ConstIterator &operator--();
ConstIterator operator--(int);
ConstIterator &operator+=(size_type);
ConstIterator operator+(size_type) const;
friend ConstIterator operator+(size_type, const ConstIterator&);
ConstIterator &operator-=(size_type);
ConstIterator operator-(size_type) const;
difference_type operator-(ConstIterator) const;
reference operator*() const;
pointer operator->() const;
reference operator[](size_type) const;
};
using ReverseIterator = std::reverse_iterator<Iterator>;
using ConstReverseIterator = std::const_reverse_iterator<ConstIterator>;
StdLibClass();
~StdLibClass();
StdLibClass(const StdLibClass&);
StdLibClass& operator=(const StdLibClass&);
StdLibClass(const StdLibClass&&);
StdLibClass& operator=(const StdLibClass&&);
bool operator==(const StdLibClass&) const;
bool operator!=(const StdLibClass&) const;
bool operator<(const StdLibClass&) const;
bool operator>(const StdLibClass&) const;
bool operator<=(const StdLibClass&) const;
bool operator>=(const StdLibClass&) const;
Iterator begin();
ConstIterator begin() const;
ConstIterator cbegin() const;
Iterator end();
ConstIterator end() const;
ConstIterator cend() const;
ReverseIterator rbegin();
ConstReverseIterator rbegin() const;
ConstReverseIterator crbegin() const;
ReverseIterator rend();
ConstReverseIterator rend() const;
ConstReverseIterator crend() const;
reference front();
const_reference front() const;
reference back();
const_reference back() const;
template <class... TArgs>
void emplace_front(TArgs &&...);
template <class... TArgs>
void emplace_back(TArgs &&...);
void push_front(const T&);
void push_front(T&&);
void push_back(const T&);
void push_back(T&&);
void pop_front();
void pop_back();
reference operator[](size_type);
const_reference operator[](size_type) const;
reference at(size_type);
const_reference at(size_type) const;
template <class... TArgs>
Iterator emplace(ConstIterator, TArgs&&...);
Iterator insert(ConstIterator, const T&);
Iterator insert(ConstIterator, T&&);
Iterator insert(ConstIterator, size_type, T&);
template <class Iter>
Iterator insert(ConstIterator, Iter, Iter);
Iterator insert(ConstIterator, std::initializer_list<T>);
Iterator erase(ConstIterator);
Iterator erase(ConstIterator, ConstIterator);
void clear();
template <class Iter>
void assign(Iter, Iter);
void assign(std::initializer_list<T>);
void assign(size_type, const T&);
void swap(StdLibClass &);
size_type size() const;
size_type max_size() const;
bool empty() const;
Alloc get_allocator() const;
};
// possibly want to specialize std::swap for the class too
namespace std
{
template <>
void swap<T, StdLibClass<T, Alloc>>(StdLibClass<T, Alloc>&, StdLibClass<T, Alloc>&);
} // namespace std
int main()
{
return 0;
}
Some I've found in webpages around the internet, others I've found by digging the standard library headers. Is there some member or some other concept I'm missing?
I think, depending on the kind of container you want to implement, the new standard requirements (standard concepts) added in C++20 are a good place to look for the required interface of a standard container.
Example for Container and especially AllocatorAwareContainer
I am getting the compiler error undefined reference to when attempting to inline operator!= as a friend function.
Here is an example:
// color.hpp
class Color
{
friend bool operator==(const Color& lhs, const Color& rhs);
inline friend bool operator!=(const Color& lhs, const Color& rhs);
};
// color.cpp
bool operator==(const Color& lhs, const Color& rhs)
{
}
inline bool operator!=(const Color& lhs, const Color& rhs)
{
}
I cannot implement the operators in the header file, as this creates multiple definition errors.
I am compiling with --std=c++11, g++ 5.2.
Remove the inline from the class definition.
After the class definition, in the header file, add the following:
inline bool
operator!=(const Color& lhs, const Color& rhs)
{
return !(lhs == rhs);
}
Remove the definition in the source file.
I've implemented a random_access_iterator for a custom library (which is templated to be reused both as const iterator and non-const iterator), but it's causing memory leaks while doing something like std::sort(container.begin(), container.end() (container.begin()/end() return iterator instance.
What's wrong with my implementation?
template <bool is_const_iterator = false>
class meta_iterator : public std::iterator<std::random_access_iterator_tag, T> {
public:
typedef T value_type;
typedef std::ptrdiff_t difference_type;
typedef typename std::conditional<is_const_iterator, const value_type &,
value_type &> reference;
typedef std::random_access_iterator_tag iterator_category;
typedef typename std::conditional<is_const_iterator, value_type const *,
value_type *>::type pointer;
typedef meta_iterator self_type;
meta_iterator(T *ptr) : ptr_(ptr) {}
meta_iterator(const meta_iterator<true> &other) : ptr_(other.ptr_) {}
self_type operator+(difference_type value) {
ptr_ += value;
return *(this);
};
self_type operator-(difference_type value) {
ptr_ -= value;
return *(this);
};
difference_type operator-(const self_type &other) {
return ptr_ - other.ptr_;
}
T &operator[](difference_type value) const { return *ptr_[value]; }
bool operator==(const self_type &other) const { return ptr_ == other.ptr_; }
bool operator!=(const self_type &other) const { return !(*this == other); }
bool operator>=(const self_type &other) const { return !((*this) < other); }
bool operator<=(const self_type &other) const { return !((*this) > other); }
bool operator<(const self_type &other) const { return ptr_ < other.ptr_; }
bool operator>(const self_type &other) const { return ptr_ < other.ptr_; }
self_type &operator=(const self_type &other) {
ptr_ = other.ptr_;
return *(this);
}
T *operator->() const { return ptr_; }
T &operator*() const { return *ptr_; }
self_type &operator--() {
ptr_--;
return *this;
}
self_type operator--(int) {
self_type temp(*this);
--(*this);
return (temp);
}
self_type &operator++() {
ptr_++;
return *this;
}
self_type operator++(int) {
self_type temp(*this);
--(*this);
return (temp);
}
self_type &operator+=(difference_type value) {
ptr_ += value;
return *(this);
}
self_type &operator-=(difference_type value) {
ptr_ -= value;
return *(this);
}
friend class meta_iterator<true>;
friend class meta_iterator<false>;
private:
T *ptr_;
};
Don't know if it causes a memory leak, but these two operators
self_type operator+(difference_type value) {
ptr_ += value;
return *(this);
};
self_type operator-(difference_type value) {
ptr_ -= value;
return *(this);
};
should just return a new iterator, and not update the stored ptr_.
Also, there is a typo in
self_type operator++(int) {
self_type temp(*this);
--(*this);
return (temp);
}
making it go in the wrong direction. That will probably confuse some loops.
// std:: iterator sample
#include <iostream> // std::cout
#include <iterator> // std::iterator, std::input_iterator_tag
class MyIterator:public std::iterator<std::input_iterator_tag, int>
{
int *p;
public:
MyIterator(int *x):p(x){}
MyIterator(const MyIterator& mit):p(mit.p){}
MyIterator& operator++(){++p; return *this;}
MyIterator operator++(int){MyIterator tmp(*this);operator++(); return tmp;}
bool operator==(const MyIterator& rhs){return p == rhs.p;}
bool operator!=(const MyIterator& rhs){return p!rhs.p;}
int& operator*(){return *p;}
};
int main(){
int numbers[] = {10, 20, 30, 40, 50};
MyIterator from(numbers);
MyIterator until(numbers+5);
for (MyIterator it=from; it!=until; it++)
std::cout << *it << '';
std::cout << '\n';
return 0;
};
When I tried to get a better understanding of what an "iterator" is. I copy such code to my compiler (codeBlock).
There is an error: "expected ';' before '!' token".
What's the matter with that?
You have a typo in operator!=:
p!rhs.p
should read
p != rhs.p
or, more generically,
!(*this == rhs)
You also have an invalid empty character constant in this line:
std::cout << *it << '';
^^ // Get rid of that, or change it to something sensible
Looks like it's this line:
bool operator!=(const MyIterator& rhs){return p!rhs.p;}
Change it to this:
bool operator!=(const MyIterator& rhs){return p != rhs.p;}
I suggest defining != as
bool operator==(const MyIterator& rhs){return p == rhs.p;}
bool operator!=(const MyIterator& rhs){return !(*this == rhs);}
So if == becomes more complicated you don't have to duplicate code in !=
Likewise you can define < > <= >= in terms of just < and ==, minimising code duplication
bool operator == (const MyIterator& rhs) const {return p == rhs.p;}
bool operator < (const MyIterator& rhs) const {return p < rhs.p;}
bool operator <= (const MyIterator& rhs) const {return (*this == rhs) || (*this < rhs);}
bool operator != (const MyIterator& rhs) const {return !(*this == rhs);}
bool operator >= (const MyIterator& rhs) const {return !(*this < rhs);}
bool operator > (const MyIterator& rhs) const {return !(*this <= rhs);}
I believe it's in the overload for the != operator. The line should be:
bool operator!=(const MyIterator& rhs){return p!=rhs.p;}
I've implemented the functionality of std::rel_ops namespace as a template base class (it defines all comparison operators using only operators < and ==). For me it's a bit weird that it works (so far) properly, also I'm concerned about the 'hacks' used. Can anyone assess the following code and say if I'm just lucky it to work or it's standard practice to do things like that.
template <typename T>
class RelationalOps {
public:
inline bool operator!=(const T &rhs) const
{
const T& lhs = static_cast<const T&>(*this);
return !(lhs == rhs);
}
inline bool operator<=(const T &rhs) const
{
const T& lhs = static_cast<const T&>(*this);
return ((lhs < rhs) || (lhs == rhs));
}
inline bool operator>(const T &rhs) const
{
const T& lhs = static_cast<const T&>(*this);
return !((lhs < rhs) || (lhs == rhs));
}
inline bool operator>=(const T &rhs) const
{
const T& lhs = static_cast<const T&>(*this);
return !(lhs < rhs);
}
};
Well, why not use Boost.Operators?