Перегрузка потоковой вставки без нарушения скрытия информации? - PullRequest
1 голос
/ 07 июня 2010

Я использую yaml-cpp для проекта. Я хочу перегрузить операторы << и >> для некоторых классов, но у меня возникла проблема, связанная с тем, как "правильно" сделать это. Взять, к примеру, класс Note. Это довольно скучно:

class Note {
  public:
    // constructors
    Note( void );
    ~Note( void );

    // public accessor methods
    void            number( const unsigned long& number ) { _number = number; }
    unsigned long   number( void ) const                  { return _number; }
    void            author( const unsigned long& author ) { _author = author; }
    unsigned long   author( void ) const                  { return _author; }
    void            subject( const std::string& subject ) { _subject = subject; }
    std::string     subject( void ) const                 { return _subject; }
    void            body( const std::string& body )       { _body = body; }
    std::string     body( void ) const                    { return _body; }

  private:
    unsigned long   _number;
    unsigned long   _author;
    std::string     _subject;
    std::string     _body;
};

Оператор << - легкий соус. В .h:

YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v );

А в .cpp:

YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v ) {
  out << v.number() << v.author() << v.subject() << v.body();
  return out;
}

Нет пота. Тогда я иду, чтобы объявить оператор >>. В .h:

void operator >> ( const YAML::Node& node, Note& note );

Но в .cpp я получаю:

void operator >> ( const YAML::Node& node, Note& note ) {
  node[0] >> ?
  node[1] >> ?
  node[2] >> ?
  node[3] >> ?
  return;
}

Если я напишу что-то вроде node[0] >> v._number;, мне нужно будет изменить CV-квалификатор, чтобы все Note поля public (что побеждает все, чему меня учили (профессора, книги и опыт)) ) о сокрытии данных.

Мне кажется, что делать node[0] >> temp0; v.number( temp0 ); повсеместно не только утомительно, подвержено ошибкам и безобразно, но довольно расточительно (что с дополнительными копиями).

Тогда я понял: я попытался переместить эти два оператора в сам класс Note и объявить их как friend s, но компилятору (GCC 4.4) это не понравилось:

src / note.h: 44: ошибка: ‘YAML :: Emitter & Note :: operator << (YAML :: Emitter &, const Note &) 'должен принимать ровно один аргумент <br> src / note.h: 45: ошибка: «void Note :: operator >> (const YAML :: Node &, Note &)» должен принимать ровно один аргумент

Вопрос: Как «правильно» перегрузить оператор >> для класса

  1. Не нарушая принцип сокрытия информации?
  2. Без чрезмерного копирования?

Ответы [ 5 ]

3 голосов
/ 07 июня 2010

Мне нравится использовать вспомогательный метод. Поскольку метод является частью класса, он будет иметь полный доступ ко всем закрытым полям:

class Note {
public:
    void read(const YAML::Node& node)
    {
        node >> ...;
    }
};

и затем operator>> просто переадресация вызова:

const YAML::Node &operator >> ( const YAML::Node& node, Note& note ) {
    note.read(node);
    return node;
}
3 голосов
/ 07 июня 2010

Типичный способ сделать это без нарушения инкапсуляции - сделать оператор >> функцией-другом. Должно быть, была проблема с синтаксисом в вашем объявлении оператора друга (не ясно, что именно из сообщения об ошибке). Я не пользуюсь YAML, но из твоего вопроса суть заключается в следующем:

class Note{
    ...
    friend void operator >> ( const YAML::Node& node, Note& note );
    ....
 };
 void operator >> ( const YAML::Node& node, Note& note ){
    node[0] >> note._number;
    node[1] >> note._author;
    node[2] >> note._subject;
    node[3] >> note._body;
 }

Функция друга имеет те же права доступа к закрытым членам, что и функция члена.

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

1 голос
/ 07 июня 2010

Ваш класс уже имеет методы установки.Просто используйте временные фильтры для чтения значений и используйте методы установки для настройки объекта:

void operator >> ( const YAML::Emitter& node, Note& note ) {
  unsigned long number;
  unsigned long author;
  // ...
  node[0] >> number;
  node[1] >> author;
  // ... everything properly read, edit the node:
  node.number(number);
  node.author(author);
  // ...
  return;

}

Некоторые другие комментарии: класс, который имеет методы установки / получения для всех атрибутов, вряд лиинкапсулируются.Вы предоставляете пользователям тот же уровень доступа, как если бы ваши поля были на самом деле общедоступными (с единственным преимуществом, что вы можете добавить проверку позже, но все же инкапсуляция слабая).

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

Потенциальное расточительное использование ресурсов, вероятно, будет очень ограниченным.Затем, как всегда, сначала измерьте, а затем попытайтесь решить проблемы, если они у вас есть.

1 голос
/ 07 июня 2010

Вы определяете другие методы установки в Note, например,

void number(YAML::Immitter& e) { e>>_number; }

и т. Д., А затем вы определяете синтаксис-сахар >> как

void operator >> ( YAML::Immitter& e, Note& note ) {
  note.number(e);
  note.author(e);
  note.subject(e);
  note.body(e);
}

Я не знаком с пространством имен YAML, которое вы используете (я знаю yaml, но я никогда не обрабатывал его в C ++), но примерно так вы поступили бы с обычными потоками (кроме void типы возврата ;-), и я уверен, что он может быть легко адаптирован к вашим потребностям.

0 голосов
/ 07 июня 2010

Хорошо, вот идея, которую вы могли бы рассмотреть. Вы говорите, что проблема с функцией non-friend, non-member << заключается в том, что она включает в себя множество объявлений tmp. Рассматривали ли вы инкапсуляцию концепции и создание вокруг нее компонента многократного использования? Использование может выглядеть примерно так: </p>


inputter& operator >> (inputter& in, my_type & obj)
{
  input_helper<my_type> helper(obj);

  in >> helper.setter(&my_type::number);
  in >> helper.setter(&my_type::subject);
  // etc
}

Ответственность input_helper состоит в том, чтобы просто предоставить шаблонную функцию setter(), которая возвращает объект, который просто читает значение и вызывает его с помощью установщика, создавая необходимую временную переменную. Код, подобный этому, потребует некоторого глубокого знакомства с шаблонами, но не будет особенно трудным. Сейчас я не могу думать совершенно прямо - возможно, простужаюсь - или я, вероятно, смогу просто напечатать это. Может быть, что-то вроде этого:


template < typename T >
struct input_helper
{
  input_helper(T & t) : obj(t) {}

  template < typename V >
  struct streamer
  {
    streamer(T & t, void (T::*f)(V const&)) : obj(t), fun(f) {}

    template < typename Stream >
    Stream& read_from(Stream & str) const // yeah, that's right...const; you'll be using a temporary.
    {
      V v;
      str >> v;
      obj.(*fun)(v);
      return str;
    }

  private: // you know the drill...
  }

  template < typename V >
  streamer setter(void (T::*fun)(V const&))
  {
    return streamer(obj, fun);
  }
private:
  T & obj;
};
// etc...  operator >> (blah blah) { return setter.read_from(stream); }

В этом наверняка есть все виды ошибок, но это должно дать вам представление. Также потребуется больше работы для обобщения.

...