Производная от std :: back_insert_iterator? - PullRequest
1 голос
/ 15 августа 2010

Я хочу получить из std::back_insert_iterator, чтобы создать своего рода фильтр для строкового типа, скажем, back_xml_insert_iterator, который будет проверять символы, передаваемые через него, в поисках символов, которые не могут быть переданы «голыми» в поток XML.Например, '"', '&', '<', '>' и '\'', и вместо этого будут вставлять ссылки на сущности своих символов, например, "&#34;" для '"'.

template<class StringType>
class back_xml_insert_iterator : public std::back_insert_iterator<StringType> {
  typedef std::back_insert_iterator<StringType> base_type;
public:
  typedef typename base_type::container_type container_type;
  typedef typename StringType::value_type value_type;

  explicit back_xml_insert_iterator( StringType &s ) : base_type( s ) { }

  back_xml_insert_iterator& operator=( value_type c ) {
    switch ( c ) {
      case '"':
      case '&':
      case '\'':
      case '<':
      case '>':
        char buf[10];
        this->container->append( "&#" );
        this->container->append( itoa( c, buf ) );
        this->container->push_back( ';' );
        break;
      default:
        this->container->push_back( c );
    }
    return *this;
  }
};

Компилируется нормально.Когда я создал экземпляр, я подтвердил, что вызывается конструктор, но мой operator=() никогда не вызывается.Я думаю, это потому, что унаследованный operator*() возвращает back_insert_iterator&, а не back_xml_insert_iterator&, поэтому back_insert_iterator::operator=() вызывается вместо моего (поскольку operator=() не является и не может быть virtual).

Если это так, то, кажется, невозможно извлечь из back_insert_iterator полезным способом.

Если я вместо этого создаю свой собственный класс back_insert_iterator_base, например:

template<class ContainerType,class DerivedType>
class back_insert_iterator_base :
  public std::iterator<std::output_iterator_tag,void,void,void,void> {
public:
  typedef ContainerType container_type;

  DerivedType& operator*() {
    return *static_cast<DerivedType*>( this );
  } 

  DerivedType& operator++() {
    return *static_cast<DerivedType*>( this );
  }

  DerivedType& operator++(int) {
    return *static_cast<DerivedType*>( this );
  }

protected:
  back_insert_iterator_base( ContainerType &c ) : container( &c ) {
  }

  ContainerType *container;
};

и получайте из этого вместо этого:

template<class StringType>
class back_xml_insert_iterator :
  public back_insert_iterator_base< StringType, back_xml_insert_iterator<StringType> > {
  // ... as before ...
};

, тогда back_xml_insert_iterator работает как нужно.Так можно ли извлечь из std::back_insert_iterator и заставить его работать как нужно?

Обновление

Вот как я хотел бы использовать back_xml_insert_iterator.Во-первых, будет вспомогательная функция:

template<class StringType> inline
back_xml_insert_iterator<StringType> back_xml_inserter( StringType &s ) {
  return back_xml_insert_iterator<StringType>( s );
}

Тогда написание функции to_xml() будет тривиальным:

template<class InputStringType,class OutputStringType> inline
void to_xml( InputStringType const &in, OutputStringType *out ) {
  std::copy( in.begin(), in.end(), back_xml_inserter( *out ) );
}

Ответы [ 3 ]

2 голосов
/ 16 августа 2010

Вы не должны пытаться наследовать от классов, которые не были разработаны с учетом наследования.В общем, вы не должны наследовать от стандартных контейнеров или итераторов.Обратите внимание, что в большинстве случаев использование итераторов происходит в шаблонах, которые в первую очередь не требуют иерархии типов.

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

0 голосов
/ 16 августа 2010

Я мог бы заставить это работать с вызовом к оператору = успешно. Я правильно понял?

// пример back_insert_iterator

#include <iostream>
#include <iterator>
#include <vector>
using namespace std;

template<class StringType> 
class back_xml_insert_iterator : public std::back_insert_iterator<StringType> { 
   typedef std::back_insert_iterator<StringType> base_type; 
public: 
   typedef typename base_type::container_type container_type; 
   typedef typename StringType::value_type value_type; 

   explicit back_xml_insert_iterator( StringType &s ) : base_type( s ) { } 

   back_xml_insert_iterator& operator=( value_type c ) { 
      switch ( c ) { 
      case '"': 
      case '&': 
      case '\'': 
      case '<': 
      case '>': 
         break; 
      default: 
         this->container->push_back( c ); 
      } 
      return *this; 
   } 
}; 

int main () {
   vector<int> firstvector, secondvector;
   for (int i=1; i<=5; i++)
   { firstvector.push_back(i); secondvector.push_back(i*10); }

   back_xml_insert_iterator< vector<int> > back_it (firstvector);

   back_it = 2;   // It does call operator=

   copy (secondvector.begin(),secondvector.end(),back_it);

   vector<int>::iterator it;
   for ( it = firstvector.begin(); it!= firstvector.end(); ++it )
      cout << *it << " ";
   cout << endl;

   return 0;
}
0 голосов
/ 16 августа 2010

Вы можете наследовать от back_insert_iterator, но вам нужно переопределить операторы, чтобы вернуть ваш производный тип.

Это делает стратегию бессмысленной? Это зависит от вашего стиля кодирования. Переопределения будут просто вызывать базовый метод и возвращать this, так что в итоге вы получите код копирования-вставки, очень похожий на то, что вы вставили в back_insert_iterator_base.

template<class StringType>
class back_xml_insert_iterator : public std::back_insert_iterator<StringType> {
  typedef std::back_insert_iterator<StringType> base_type;
public:
  typedef typename base_type::container_type container_type;
  typedef typename StringType::value_type value_type;

    // add trivial overrides to avoid returning base type:
  back_xml_insert_iterator &operator++() { base::operator++(); return this; }
  back_xml_insert_iterator operator++(int) {
      back_xml_insert_iterator ret( *this );
      base::operator++();
      return ret;
  }

  explicit back_xml_insert_iterator( StringType &s ) : base_type( s ) { }

  ...

Конечно, подобный блок кода может реализовывать передачу вызовов оператора члену back_insert_iterator, что исключит наследование, как рекомендуют другие. Возможно, это лучший подход.

Обратите внимание, что частное наследование с : private std::back_insert_iterator повысило бы безопасность, запретив пользователям пытаться использовать ваш класс как нечто, чем оно не является.

...