Skip to main content
added 24 characters in body
Source Link
Jerry Coffin
  • 44.7k
  • 5
  • 91
  • 163

Let's consider, for example, a simple iterator hierarchy that does use iterationinheritance and virtual functions:

Let's consider, for example, a simple iterator hierarchy that does use iteration:

Let's consider, for example, a simple iterator hierarchy that does use inheritance and virtual functions:

Source Link
Jerry Coffin
  • 44.7k
  • 5
  • 91
  • 163

You've already gotten answers pointing to why it's not necessary for all iterators to inherit from a single Iterator base class. I'd got quite a bit further though. One of the goals of C++ is abstraction with zero run-time cost.

If iterators worked by all of them inheriting from a common base class, and used virtual functions in the base class to define the interface, and the derived classes provided implementations of those virtual functions, that could (and often would) add substantial run-time overhead to the operations involved.

Let's consider, for example, a simple iterator hierarchy that does use iteration:

template <class T>
class iterator_base { 
public:
    virtual T &operator*() = 0;
    virtual iterator_base &operator++() = 0;
    virtual bool operator==(iterator_base const &other) { return pos == other.pos; }
    virtual bool operator!=(iterator_base const &other) { return pos != other.pos; }
    iterator_base(T *pos) : pos(pos) {}
protected:
    T *pos;
};

template <class T>
class array_iterator : public iterator_base<T> {
public: 
    virtual T &operator*() override { return *pos; }
    virtual array_iterator &operator++() override { ++pos; return *this; }
    array_iterator(T *pos) : iterator_base(pos) {}
};

Then let's give it a quick test:

int main() { 
    char input[] = "asdfasdfasdfasdfasdfasdfasdfadsfasdqwerqwerqwerqrwertytyuiyuoiiuoThis is a stringy to search for something";
    using namespace std::chrono;

    auto start1 = high_resolution_clock::now();
    auto pos = std::find(std::begin(input), std::end(input), 'g');
    auto stop1 = high_resolution_clock::now();

    std::cout << *++pos << "\n";

    auto start2 = high_resolution_clock::now();
    auto pos2 = std::find(array_iterator(input), array_iterator(input+sizeof(input)), 'g');
    auto stop2 = high_resolution_clock::now();

    std::cout << *++pos2 << "\n";

    std::cout << "time1: " << duration_cast<nanoseconds>(stop1 - start1).count() << "ns\n";
    std::cout << "time2: " << duration_cast<nanoseconds>(stop2 - start2).count() << "ns\n";
}

[note: depending on your compiler, you may need to do a bit more, such as defining the iterator_category, difference_type, reference, and so on, for the compiler to accept the iterator.]

And the output is:

y
y
time1: 1833ns
time2: 2933ns

[Of course, if you run the code, your results won't match these exactly.]

So, even for this simple case (and doing only about 80 increments and comparisons) we've added around 60% overhead to a simple linear search. Especially when iterators were originally added to C++, quite a few people simply wouldn't have accepted a design with that much overhead. They probably wouldn't have been standardized, and even if they had, virtually nobody would use them.