Информация об изменениях

Сообщение Re[3]: наборы от 25.09.2023 14:34

Изменено 25.09.2023 14:37 Sm0ke

Re[3]: наборы
Здравствуйте, Евгений Музыченко

Это обсуждение привело меня к размышлениям над смежной темой.
А именно об отделдении структур для хранения данных от
набора действий над ними.

Вот к примеру есть некий класс, который что-то хранит.
Если все действия, сваязяанные с его обработкой, запихать как методы в него
то он начинает разрастаться и "мозолить взгляд"

Мне знакомо пожелание разработчика определить общие действия над разными данными
, и как-то их сгруппировать.

В итоге я написал вот такой простенький пример — один из вариантов — как это можно осуществить.
link: https://godbolt.org/z/q3x7TcfE5
#include <type_traits>
#include <iostream>

struct t_file;
struct t_console;
struct restrict;

template <typename Type>
struct io_actions
{
  using pointer = Type *;

  // data
  pointer self;

  // methods
  void read(restrict & p);
  void write(restrict & p);

  // hint
  io_actions * operator -> () { return this; }
};

struct restrict
{
  friend void io_actions<t_console>::write(restrict &);
  friend void io_actions<t_console>::read(restrict &);
  friend void io_actions<t_file>::write(restrict &);
  friend void io_actions<t_file>::read(restrict &);
private:
  int value{55};
};

struct t_console
{
  using trait = io_actions<t_console>;

  // data
  std::istream * in{& std::cin};
  std::ostream * out{& std::cout};

  trait operator -> () { return {this}; }
};

template <> void t_console::trait::read(restrict & p) { (*self->in) >> p.value; }
template <> void t_console::trait::write(restrict & p) { (*self->out) << p.value; }

int main()
{
  restrict v;
  t_console c;
  c->write(v);
  return 0;
}


Что тут происходит?
В траите io_actions только два метода read() и write(), которые работают с приватными данными из некого класса restrict

Понятно, что в дальнейшем можно определить и дополнительные отдельные траиты (наборы действий), скажем преобразования, проверка, да что угодно
, и оформить их как отдельный nest.

Почему я оформил trait как шаблонный?
Просто в данном случае конкретый обработчик может быть разным (консоль, файл, сеть, ещё куда)
И тут сами конкретные обработчики тоже не переблоачены тучей кучей методов) а просто что-то хранят

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

Кстати, в примере выше нет definition для класса t_file
Вроде не сложно его сделать по анологии, если он вообще нужен
А действия тогда будут заданы как-то так:
template <> void t_file::trait::read(restrict & p) { /* ... */ }
template <> void t_file::trait::write(restrict & p) { /* ... */ }


p/s
Оператор стрелка у t_console создаёт временный объект-wrapper, это вообще-то под вопросом — как лучше можно оформить)
Re[3]: наборы
Здравствуйте, Евгений Музыченко

Это обсуждение привело меня к размышлениям над смежной темой.
А именно об отделдении структур для хранения данных от
набора действий над ними.

Вот к примеру есть некий класс, который что-то хранит.
Если все действия, сваязяанные с его обработкой, запихать как методы в него
то он начинает разрастаться и "мозолить взгляд"

Мне знакомо пожелание разработчика определить общие действия над разными данными
, и как-то их сгруппировать.

В итоге я написал вот такой простенький пример — один из вариантов — как это можно осуществить.
link: https://godbolt.org/z/q3x7TcfE5
#include <type_traits>
#include <iostream>

struct t_file;
struct t_console;
struct restrict;

template <typename Type>
struct io_actions
{
  using pointer = Type *;

  // data
  pointer self;

  // methods
  void read(restrict & p);
  void write(restrict & p);

  // hint
  io_actions * operator -> () { return this; }
};

struct restrict
{
  friend void io_actions<t_console>::write(restrict &);
  friend void io_actions<t_console>::read(restrict &);
  friend void io_actions<t_file>::write(restrict &);
  friend void io_actions<t_file>::read(restrict &);
private:
  int value{55};
};

struct t_console
{
  using trait = io_actions<t_console>;

  // data
  std::istream * in{& std::cin};
  std::ostream * out{& std::cout};

  trait operator -> () { return {this}; }
};

template <> void t_console::trait::read(restrict & p) { (*self->in) >> p.value; }
template <> void t_console::trait::write(restrict & p) { (*self->out) << p.value; }

int main()
{
  restrict v;
  t_console c;
  c->write(v);
  return 0;
}


Что тут происходит?
В траите io_actions только два метода read() и write(), которые работают с приватными данными из некого класса restrict

Понятно, что в дальнейшем можно определить и дополнительные отдельные траиты (наборы действий), скажем преобразования, проверка, да что угодно
, и оформить их как отдельный nest.

Почему я оформил trait как шаблонный?
Просто в данном случае конкретый обработчик может быть разным (консоль, файл, сеть, ещё куда)
И тут сами конкретные обработчики тоже not_bloated тучей кучей методов) а просто что-то хранят

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

Кстати, в примере выше нет definition для класса t_file
Вроде не сложно его сделать по анологии, если он вообще нужен
А действия тогда будут заданы как-то так:
template <> void t_file::trait::read(restrict & p) { /* ... */ }
template <> void t_file::trait::write(restrict & p) { /* ... */ }


p/s
Оператор стрелка у t_console создаёт временный объект-wrapper, это вообще-то под вопросом — как лучше можно оформить)