7
\$\begingroup\$

For educational purposes and to understand how iterators are working in C++, I've create an input iterator and I would like others to look at and provide any feedback on my code.

template<typename T>
class iter_input {
 public:
  typedef T         value_type;
  typedef T&        reference_type;
  typedef const T&  const_reference_type;
  typedef T*        pointer_type;
  typedef const T*  const_pointer_type;

  iter_input(pointer_type& pointer) : pointer_(pointer) {}
  iter_input(const iter_input& other) : pointer_(other.pointer_) {}

  iter_input& operator= (const iter_input& rhs) {
    this->pointer_ = rhs.pointer_;
    return (*this);
  }

  iter_input& operator++ (void) {
    ++this->pointer_;
    return (*this);
  }

  iter_input operator++ (int) {
    iter_input temp(*this->pointer_);
    ++*this;
    return (temp);
  }

  bool operator== (const_reference_type rhs) {
    return (this->pointer_ == rhs.pointer_);
  }

  bool operator!= (const_reference_type rhs) {
    return (this->pointer_ != rhs.pointer_);
  }

 private:
  T* pointer_;
};

References:

\$\endgroup\$
9
\$\begingroup\$

Correctness Issues

Needs dereferencing operator

There are two basic operations every iterator needs to support:

  1. Incrementing the iterator
  2. Dereferencing the iterator

Right now, you've covered incrementing, but not dereferencing. That means you can iterate over items, but you can't look at the items themselves, rendering the whole thing nearly useless (I would remove the "nearly", but you can compute the distance between two iterators, and once in a while that's all you really need).

reference_type operator *() { return *pointer_; }

const correctness

operator ==() and operator !=() should both be const operators.

Correct types

operator== and operator!= should take parameters of type iter_input as their parameters. When you do a comparison, you compare the iterator to another iterator, not the iterator to the type of object the iterator refers to.

These operators should look something like this:

bool operator== (iter_input rhs) const {
  return pointer_ == rhs.pointer_;
}

bool operator!= (iter_input rhs) const {
  return pointer_ != rhs.pointer_;
}

Constructor

The constructor that takes a pointer should take it by value rather than reference. This allows (for example) initializing an iterator from the name of an array:

int x[] = {1, 2, 3, 4};

iter_input<int> begin(x);
// or: iter_input<int> begin = std::begin(x);

You can normally take for granted that pointers are cheap to copy, so you don't normally want to pass pointers by reference for the sake of efficiency (but for things like linked lists, you do sometimes want to pass them by reference because you're going to modify the pointer that's passed).

Style Issues

this-> considered harmful

Using this-> for every reference to a member is (at least in my opinion) a poor idea. It adds visual noise, which hurts readability but provides no benefit.

return is an operator, not a function

When you return a value from a function, you don't need to enclose that expression in parentheses. Again, this simply adds visual noise and hurts readability without any benefit (and in a few cases that don't apply here can cause other problems as well).

Assignment operator

Unless you need compatibility with older (pre C++11) compilers I'd prefer to define the assignment operator as defaulted rather than including a function body for it.

iter_input& operator= (const iter_input& rhs) = default;

Use your own typedefs

Instead of T *pointer_;, I'd prefer pointer_type pointer_;.

Make it a proper header

If you want to do anything more than a minimal test of this code, you probably want it to be in a header. To facilitate that, you could add the (officially not exactly standard, but almost universally accepted) #pragma once at the beginning, or else add standard "header guards":

#ifndef ITER_INPUT_INCLUDED_
#define ITER_INPUT_INCLUDED_

// existing code here

#endif

Note that if you look at your standard headers, the name they use in place of ITER_INPUT_INCLUDED_ will typically start with an underscore. That gives a name that's reserved for the implementation, which means the people writing the compiler can (and in this case should) use that kind of name, but in normal code that's not part of the standard library, you should not use such a name.

Indentation

The 2-space indentation is too small for me to be certain what's supposed to line up with what. The access specifiers (public: and private:) being indented only one space makes this even worse. The days of 40-column displays are long gone. There's no need to skimp on indentation to this degree.

\$\endgroup\$
  • \$\begingroup\$ thanks for your review. Very informative. I've updated my code and before post it in here for 2nd review, I had a question about "Correct Types" section. Looking at standard [link](en.cppreference.com/w/cpp/language/operator_comparison], I need to pass rhs as reference and not pointer. This changes the comparison to return *this->pointer == rhs bool operator== (const_reference_type rhs) { return (*this->pointer_ == rhs.pointer_); } \$\endgroup\$ – Orion Mar 5 '16 at 17:06
0
\$\begingroup\$

Notes:

template<typename T>
class iter_input
{
    public:
    typedef T value_type;
    // An iterator should have a typedef for a reference not a reference_type: 
    // typedef T& reference_type;
    typedef T& reference;

    // And iterator has only one reference type:
    // typedef const T& const_reference_type;

    // Again, an iterator should have a typedef for a pointer not a pointer_type: 
    // typedef T* pointer_type;
    typedef T* pointer;

    // Again, an iterator has only one pointer type:
    // typedef const T* const_pointer_type;

    // Using pointer and solving name clashes
    // Note: the reference to the pointer is useless
    // iter_input(pointer_type& pointer)
    iter_input(pointer p)
        : pointer_(p)
    {}

    // Rule of zero, do not declare/define copy constructors and assignment 
    // operators needlessly
    // iter_input(const iter_input& other)
    //  : pointer_(other.pointer_)
    // {}

    // iter_input& operator=(const iter_input& rhs)
    //  {
    //  this->pointer_ = rhs.pointer_;
    //  return ( *this);
    // }

    // Get rid of c-style (void) (having a "this->" is a matter of taste)
    // iter_input& operator++(void)
    iter_input& operator++()
    {
        ++this->pointer_;
        return *this;
    }

    // Use the copy constructor for the temporary object
    // iter_input operator++(int)
    // {
    //   iter_input temp( *this->pointer_);
    //   ++ *this;
    //   return (temp);
    // }
    iter_input operator++(int)
    {
        iter_input temp(*this);
        ++(*this);
        return temp;
    }

    // This is wrong, compare to another iterator (not to the content)
    // bool operator==(const_reference_type rhs)
    // {
    //  return (this->pointer_ == rhs.pointer_);
    // }

    // You may consider a freestaing or friend function.
    bool operator==(iter_input rhs)
    {
        return (pointer_ == rhs.pointer_);
    }

    // Keep it simple and use  "! operator == (...)". Again, compare to another iterator.
    // bool operator!=(const_reference_type rhs)
    // {
    //  return (this->pointer_ != rhs.pointer_);
    // }

    bool operator!=(iter_input rhs)
    {
        return ! operator == (rhs);
    }

    private:
    T* pointer_;
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.