Почему указатель из класса переопределения использует оператор == и оператор! = из базового класса

У меня есть шаблон Iterator для класса и класса для использования в инструкции for.

template<class T>
class Itr2 {
public:
     Itr2()  { }
    ~Itr2()  { }

    typedef typename Itr2 type;
    typedef typename T& reference;

    virtual type& operator++()                      { return *this; }
    virtual T&    operator*()                       { return ((reference)*((type*)this)); }
    virtual bool  operator==(const type& o) const { return true; }
    virtual bool  operator!=(const type& o) const { return false; }
};


template<class T>
class I2
{
public:
    typedef I2<T> type;
    typedef T     value;
    typedef T& reference;
    typedef typename Itr2<T> iterator;

    virtual iterator& begin() { return *(new iterator()); }
    virtual iterator& end()   { return *(new iterator()); }
};

Затем я создал класс для стандартного std::vector‹>.

template<class T>
class ItrSTD : public Itr2<T> {
public:
    typedef typename Itr2<T> base_type;
    typedef typename ItrSTD<T> type;
    typedef typename T& reference;
    typedef typename std::vector<T>::iterator std_itr;
protected:
    std_itr itr_;
public:
    ItrSTD(const type& o)                               { itr_ = o.itr_; }
    ItrSTD(const std_itr& o)                            { itr_ = o; }

    virtual base_type&  operator++()                    { itr_++; return *this; }

    virtual T&          operator*()                     { return ((reference)(*this->itr_)); }
    bool  operator==(const base_type& o) const override { return (((const type&)o).itr_ == this->itr_); }
    bool  operator!=(const base_type& o) const override { return (((const type&)o).itr_ != this->itr_); }
};



template<class T>
class VSTD : public I2<T> {
protected:
    std::vector<T>  arr_;
public:
    typedef typename ItrSTD<T>  iterator;

    VSTD(const VSTD& o)  { arr_ = o.arr_; }
    template<typename ...E> VSTD(E&&...e) : arr_({ std::forward<T>(e)... }) {  }

    iterator& begin()  _NOEXCEPT  override{ return (*new iterator(arr_.begin())); }
    iterator& end()    _NOEXCEPT  override{ return (*new iterator(arr_.end())); }

};

Если я использую прямой оператор for (int i: v). Это работает нормально, но когда я пытаюсь сделать это из компилятора указателя, используйте оператор базового класса! = (не оператор переопределения! =), и код не работает :(.

int v_i = 0;
VSTD<int> vstd_a = { 1, 2, 3 };
I2<int> *i2 = &vstd_a;

for (int j : *i2) //DOESN't work :( use operator!= from base class
{
v_i += j;
}
for (int j : vstd_a) //work fine :)  use operator!= from VSTD.
{
 v_i += j;
}

Если я упрощу код:

template<typename T>
class I3
{
public:
    T i;
    virtual bool  operator==(const I3& o) const { return false; }
};

template<typename T>
class I3O : public I3<T>
{
public:
    virtual bool  operator==(const I3& o) const override { return true; }
};

I3O<int> i3_a, i3_b; I3<int> *i3_ap, *i3_bp;

i3_ap = &i3_a; i3_bp = &i3_b; bool i3_c;

i3_c = (i3_a == i3_b);
i3_c = ((*i3_ap) == (*i3_bp));

Оба результата в порядке (возвращают true) и сравниваются с классом переопределения (только for( : ) работают плохо :( :

Почему это происходит. Можно ли использовать оператор for для указателя на шаблон? Функции begin(), end() работают нормально. Только операторы работают разные.

P.S. Я использую компилятор VS2013.


person Arkadiusz Klemenko    schedule 18.11.2014    source источник
comment
На самом деле вы не возвращаете *new ... в функции, которая возвращает ссылку, не так ли? Это почти гарантированная утечка памяти.   -  person James Kanze    schedule 18.11.2014
comment
деструктор класса Itr2, вероятно, должен быть virtual, возможно, даже чистым virtual.   -  person John Dibling    schedule 18.11.2014


Ответы (2)


Стандарт определяет семантику оператора for на основе диапазона в [stmt.ranged]/1:

Для основанного на диапазоне оператора for формы

for ( for-range-declaration : expression ) statement

пусть range-init будет эквивалентен выражению, заключенному в круглые скобки

( expression )

и для основанного на диапазоне оператора for формы

for ( for-range-declaration : braced-init-list ) statement

пусть range-init будет эквивалентен braced-init-list. В каждом случае оператор for на основе диапазона эквивалентен

{
  auto && __range = range-init;
  for ( auto __begin = begin-expr,
             __end = end-expr;
        __begin != __end;
        ++__begin ) {
    for-range-declaration = *__begin;
    statement
  }
}

где __range, __begin и __end — переменные, определенные только для описания, а _RangeT — тип выражения, а begin-expr и end-expr определяются следующим образом:

(1.1) — если _RangeT является типом массива, ...

(1.2) — если _RangeT является типом класса, неквалифицированные идентификаторы begin и end просматриваются в области видимости класса _RangeT, как если бы поиск доступа к члену класса (3.4.5), и если один (или оба) находит по крайней мере одно объявление, begin-expr и end-expr равны __range.begin() и __range.end() соответственно;

(1.3) — иначе, ...

Первый цикл в вашей программе:

for (int j : *i2) //DOESN't work :( use operator!= from base class
{
v_i += j;
}

таким образом, эквивалентно:

{
  auto && __range = (*i2);
  for ( auto __begin = __range.begin(),
             __end = __range.end();
        __begin != __end;
        ++__begin ) {
    int j = *__begin;
    v_i += j;
  }
}

Поскольку возвращаемый тип I2<int>::begin и I2<int>::end равен Itr2<int>&, __begin и __end будут выведены как тип Itr2<int>, а копия построена из возвращаемых значений I2<int>::begin() и I2<int>::end(). Эта копирующая конструкция, конечно, нарезает возвращаемые типы, и в итоге вы получаете экземпляры базового типа.

person Casey    schedule 18.11.2014
comment
Можно сообщить компилятору, что это значение из указателя: auto && __range = (*i2); VSTD‹T›, а не I2‹T›? Я хочу создать функцию типа function(I‹T› *i); вычислить для всех типов итераторов. Но единственное, что я могу сделать, это использовать функцию template‹T›(T *i); - person Arkadiusz Klemenko; 19.11.2014

Я не смотрел детали в вашем коде, но... Похоже, вы пытаетесь сделать итераторы полиморфными. Однако стандарт постоянно передает их по значению, а передача по значению не поддерживает полиморфизм; он режет. Таким образом (я предполагаю), в:

for ( int j : *i2 )

компилятор создает для итератора локальные переменные, тип которых определяется статически. И хотя он вызывает правильный begin(), он присваивает результаты этой локальной переменной, тем самым нарезая ее.

Если вам нужны полиморфные итераторы, вам нужно реализовать идиому письмо/конверт:

class Iterator
{
    Iterator* myImpl;

    virtual Iterator* clone() const { abort(); }
    virtual T& current() const { abort(); }
    virtual bool isEqual( Iterator const* other ) { abort(); }
    virtual void next() { abort(); }

protected:
    Iterator() : myImpl( nullptr ) {}
public:
    Iterator( Iterator* impl ) : myImpl( impl ) {}
    Iterator( Iterator const& other ) : myImpl( other.clone() ) {}
    virtual ~Iterator() { delete myImpl; }

    T& operator*() const
    {
        return myImpl->current();
    }
    bool operator==( Iterator const& other ) const
    {
        return myImpl->isEqual( other.myImpl );
    }
    Iterator& operator++()
    {
        myImpl->next();
        return *this;
    }
    //  ...
};

class DerivedIterator : public Iterator
{
    Iterator* clone() const override { return new DerivedIterator( *this );  }
    T& current() const override { ... }
    bool isEqual( Iterator const* other ) override { ... }
    virtual void next() { ... }
public:
    DerivedIterator(...) ...
};

Затем ваши производные begin и end возвращают что-то вроде:

Iterator( new DerivedIterator(...) );

Это довольно дорого во время выполнения, но на самом деле это единственный способ обеспечить полиморфизм и по-прежнему иметь семантику значений, требуемую итераторами.

person James Kanze    schedule 18.11.2014