На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> включение методов класса
    У меня есть несколько классов. Для удобства упращу их описание.

    ExpandedWrap disabled
      // базовый класс для чтения данных
      class reader_base abstract
      {public:
      virtual bool read( data_type& data ) = 0;
      };
       
      // базовый класс для записи данных
      class writer_base abstract
      {public:
      virtual bool write( const data_type& data ) = 0;
      };
       
      // базовый класс для чтения и записи данных
      class full_base abstract:
      public reader_base,
      public writer_base
      {
      };


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

    Существует варианты основанные на паттернах "адаптер" и "декоаратор". Но это скучно и много кода.

    Подумалось, что есть ещё один интересный вариант. Это шаблонный дочерний класс.
    ExpandedWrap disabled
      template< typename BasicType >
      class basic_file final:
      public BasicType
      {
      public:
       
      bool read( data_type& data ) override final;
       
      bool write( const data_type& data ) override final;
       
      };
       
      // читает из файла
      typedef basic_file< reader_base > file_reader;
       
      // пишет в файл
      typedef basic_file< writer_base > file_writer;
       
      // читает и пишет в файл
      typedef basic_file< full_base > file_full;


    И вроде как всё понятно, кроме одного места.
    Можно ли включать методы read и write в дочернем классе, только тогда, когда они необходимы?

    Например для basic_file<reader_base> и basic_file<full_base>, метод read нужен обязательно.
    А для basic_file<writer_base> вообще не нужен.

    Интересует возможность включения и выключения общими стандартными средствами. Про ms specific я знаю.

    На счёт enable_if задумывался, но есть сомнение, что оно сработает корректно. Впрочем, надо будет попробовать.

    Вариант со специализацией всего класса не предлагать, ибо это будет усложнённый вариант с прямым наследованием. В самом деле, я ведь и руками могу сделать file_reader, file_writer, file_full, но это скучно.
      Шаблонные методы + enable_if_t?
        Цитата Flex Ferrum @
        Шаблонные методы + enable_if_t?

        А как можно при помощи enable_if скрыть виртуальный метод, да ещё помеченный как override?
          Цитата Flex Ferrum @
          Шаблонные методы + enable_if_t?


          Тут всё же не сфинаге... Выбора-то нет. Поэтому получаю:
          Цитата

          error C2039: type: не является членом "std::enable_if<false,_Ty>"

          То есть вместо выключения метода, я получаю ошибку.

          А вот конструкция
          ExpandedWrap disabled
            __if_exists( basic_type::read )
            {
            bool read( data_type& read ) override final;
            }

          Отработала успешно.
          Главный недостаток конструкции в отсутствии поддержки сторонними компиляторами.
          Сообщение отредактировано: Eric-S -
            Как обычно, нихрена не понял.
              Цитата Qraizer @
              Как обычно, нихрена не понял.

              В конечном итоге, хотелось бы получить три класса:
              ExpandedWrap disabled
                // читает из файла
                class file_reader final:
                public reader_base
                {
                  public:
                  bool read( data_type& data ) override final;
                };
                 
                // пишет в файл
                class file_writer final:
                public writer_base
                {
                  public:
                  bool write( const data_type& data ) override final;
                };
                 
                 
                // читает и пишет в файл
                class file_full final:
                public reader_base,
                public writer_base
                {
                  public:
                  bool read( data_type& data ) override final;
                  bool write( const data_type& data ) override final;
                };


              Кроме различающихся методов read и write, есть набор одинаковых методов, которые лениво дублировать.

              Для решения этой задачки, я знаю несколько способов.
              Например с помощью вложенного объекта и множественного виртуального наследования.

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

              Но поскольку дублировать код мне лень, то я замутил шаблоны. Из одного шаблона, сразу получаю три класса.
              Только вот печалька, в некоторых вариантах дочернего класса, появляются лишние методы.

              Можно рассматривать эту задачку, как своеобразный изврат. Старый вариант тоже работает. Просто новый, немного быстрее и менее требователен к памяти.
                Я думаю, что тебе стоит посмотреть, как это сделано в STL. Где, по сути, то, что фактически пишет (stream_buffer) и то, что предоставляет интерфейс пользователю (i/ostreams) логически разделены. stream_buffer поддерживает весь набор операций чтения/записи, специфичный для заданного устройства. iostreams - фасад над ним, который предоставляет логический набор операций, специфичный для конкретного варианта использования.

                Я не вижу (в твоём случае) особой необходимости ограничивать набор операций, реализуемых конкретным производным классом. По идее, пользователь не будет пользоваться его интерфейсом напрямую. Пользователь (в основном) будет использовать абстрактные интерфейсы, где read- и write-операции разделены.
                  Общие методы выносишь в единый класс, который наследуешь приватно, интерфейсы наследуешь публично и виртуально. Это классическое решение. Почему оно не устраивает?
                    Цитата Flex Ferrum @
                    Я думаю, что тебе стоит посмотреть, как это сделано в STL. Где, по сути, то, что фактически пишет (stream_buffer) и то, что предоставляет интерфейс пользователю (i/ostreams) логически разделены. stream_buffer поддерживает весь набор операций чтения/записи, специфичный для заданного устройства. iostreams - фасад над ним, который предоставляет логический набор операций, специфичный для конкретного варианта использования.


                    С этого и начинал. Там сделано прикольно. Достойно для подрожания.
                    Но при всех плюсах, есть минусы, которые мне хотелось бы переделать.

                    Объясню, как сам понимаю, то что сделано в потоках stl.

                    1. basic_streambuf - абстрактный сервисный класс, для конкретного представления.

                    2. его наследники, для конкретных представлений:
                    basic_filebuf - для работы с файлом.
                    basic_stringbuf - для работы с памятью.

                    3. указатель на объект представления, хранится в объекте basic_ios.

                    4. basic_istream наследует basic_ios и реализует обёртку вызовов методов чтения представления.

                    5. basic_ostream наследует basic_ios и реализует обёртку вызовов методов записи представления.

                    6. затем оно всё наследуется в специальные классы, например, basic_ifstream или basic_ofstream, а потом объеденяются в классы basic_fstream.

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

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

                    Вместо одного класса, надо делать целых четыре класса.

                    И да, ещё один момент. Так или иначе, нужно в классе буфера, реализовывать методы чтения и записи, даже если, где-то оно не может быть реализовано в принципе.
                    Понятно, что там ставится тупая заглушка, но в итоге, вся идиология идёт лесом.

                    Я же как раз пытаюсь зайти с другой стороны. Сделать один класс, который бы давал эффект, словно три разных класса.

                    Цитата Flex Ferrum @
                    Я не вижу (в твоём случае) особой необходимости ограничивать набор операций, реализуемых конкретным производным классом.


                    О! Вот это действительно верное замечание!
                    Тем более, у меня все те virtual методы, сделаны protected.

                    Но получается, что в исходном коде, болтаются методы, которые вообще никак не используются и даже не могут быть вызваны.
                    И пусть это даже примитивная обёртка для вызова функции, но когда их собирается больше, они раздувают исполняемый файл.
                    А я придерживаюсь философии: "что не нужно, то не надо"

                    Цитата Flex Ferrum @
                    По идее, пользователь не будет пользоваться его интерфейсом напрямую. Пользователь (в основном) будет использовать абстрактные интерфейсы, где read- и write-операции разделены.


                    Здесь слово "в основном", является важным.
                    Так же как в правиле "если пользователь может сделать, что-то не правильно, то он обязательно сделает, что-то не правильно".

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

                    Добавлено
                    Цитата Qraizer @
                    Общие методы выносишь в единый класс, который наследуешь приватно, интерфейсы наследуешь публично и виртуально. Это классическое решение. Почему оно не устраивает?

                    Я писал только о классах file_reader, file_writer, file_stream.
                    В этом, единичном, случае, предложенный вариант чудесно работает.

                    На самом деле, мне надо сделать разные классы.
                    Там, предложенный вариант, тоже будет чудесно работать.
                    Но в итоге, работы получится слишком много.
                    Причём, однообразной и нудной.
                      Eric-S, тебе надо чётко разделить интерфейс между твоими классами и пользователем, и интерфейс между твоими классами и реализациями конкретных ридеров/райтеров. Это два разных интерфейса. Как только ты поймёшь, где - какой, ты решишь свою задачу. :)
                        Цитата Flex Ferrum @
                        Eric-S, тебе надо чётко разделить интерфейс между твоими классами и пользователем, и интерфейс между твоими классами и реализациями конкретных ридеров/райтеров. Это два разных интерфейса. Как только ты поймёшь, где - какой, ты решишь свою задачу. :)


                        Угу. Верно. Но это вроде и так понятно.
                        А вот реализация, конкретных ридеров и врайтеров... Сразу написал, что могу её сделать, как отдельные классы.
                        Оно, так, получается, конечно же правильнее.
                        Но эти классы, выходят очень похожими.

                        А если делать класс реализации сразу двух интерфейсов reader_base и writer_base, то код вообще дублируется.

                        Сразу напрашивается, сделать один шаблон, для трёх разных реализаций.

                        Например, метод close, для всех трёх идентичный.
                        Метод open, наоборот, для всех разный, но я сделал специализацию метода.

                        А вот методы read и write, выпадают из этой красивой схемки.
                        Сообщение отредактировано: Eric-S -
                          Цитата Eric-S @
                          Угу. Верно. Но это вроде и так понятно.

                          Нет. Не понятно. :) Ты, уже выделев пользовательские интерфейсы в отдельные классы, всё ещё пытаешься интерфейсы реализации тоже сделать публичными. Хотя задачка решается очень просто, по сути. Пользователь использует то, что помечено как abstract. А вот финальные классы использовать (в явном виде) не может. В этом случае задача сводится к тривиальной - абстрактные интерфейсы + их фабрика.
                            Цитата Flex Ferrum @
                            Нет. Не понятно. :) Ты, уже выделев пользовательские интерфейсы в отдельные классы, всё ещё пытаешься интерфейсы реализации тоже сделать публичными. Хотя задачка решается очень просто, по сути. Пользователь использует то, что помечено как abstract. А вот финальные классы использовать (в явном виде) не может. В этом случае задача сводится к тривиальной - абстрактные интерфейсы + их фабрика.


                            Ой, что-то я не понял захода. Извиняюсь, если туплю.

                            1. сделать публичными реализацию интерфейса?
                            В самом начале, я оговорился, что примеры упрощаю.
                            На самом деле интерфейс реализации у меня скрыт от пользователя.

                            ExpandedWrap disabled
                              class reader_base
                              {
                              public:
                               
                              bool read( data_type& data )
                              {
                              return do_read( data );
                              }
                               
                              protected:
                               
                              virtual bool do_read( data_type& data ) = 0;
                               
                              };
                               
                              class file_reader:
                              public reader_base
                              {
                              public:
                               
                              protected:
                               
                              bool do_read( data_type& data ) override final
                              {
                              return file_read( m_handle, data );
                              }
                               
                              private:
                               
                              file_handle m_handle;
                               
                              };


                            Или речь идёт о чём-то другом?


                            2. использовать фабрику для пользователя?
                            Тут, так и не так одновременно.

                            Я бы хотел разделить проблематику на две части.

                            A. вложенный пользовательский класс или функция, получает и работает с указателями на интерфейс. Например reader_base или writer_base. Доступа к объекту реализации они не имеют.

                            B. пользователь же работает с классом реализации. Кроме интерфейса, есть ещё дополнительные функции, спецефичные для реализации.

                            // функция пишет данные используя интерфейс и ей плевать на конкретную реализацию)
                            ExpandedWrap disabled
                              void foo( writer_base* p_writer )
                              {
                              data_type obj;
                              p_writer->write( obj );
                              }
                               
                              int main()
                              {
                               
                              /* открыть файл используя конкретную реализацию. */
                              file_writer o_writer
                              o_writer.open( "out.dat" );
                               
                              /* записать данные, используя интерфейс. */
                              foo( &o_writer );
                               
                              /* закрыть файл, используя реализацию. */
                              o_writer.flush();
                              o_writer.close();
                              return 0;
                              }


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


                            3. ладно, допустим делать фабрикой. Фиг с ним, может так будет действительно лучше.

                            ExpandedWrap disabled
                              reader_base* reader_fabric()
                              {
                              return new file_reader();
                              }


                            А где взять этот самый класс file_reader?
                            Если класса нет, то фабрика объект не создаст.
                            Сообщение отредактировано: Eric-S -
                              Цитата Eric-S @
                              Например, если я хочу создать альтернативное представления для работы с консолью basic_consolebuf, то мне всё равно придётся реализовывать классы basic_consolestream.
                              Тоже самое произойдёт для пайпов, маилслотов, сокетов, каналов и всякой прочей фигни.
                              Нет. Разделение на форматирующие и собственно потоковые в stl сделано не для того же, что (вроде бы) нужно тебе. Оно сделано, потому что это на самом деле разная функциональность – форматирование, т.е. перевод из внутреннего представления в бинарное и обратно, и ввод-вывод, т.е. собственно обмен бинарным потоком данных с внешним хранилищем, при этом ещё и нефиксированным. В результате у потоковых классов своя иерархия, у форматирующих своя, и вторые просто используют первые.
                              Основные форматирующие классы – это std::basic_istream<>, std::basic_ostrea<> и std::basic_iostream<>, и они принимают в конструкторе любой std::basic_streambuf<>. Тот факт, что в подавляющем большинстве случаев программистам нужны потоки данных с файлами и форматирование в памяти, отражён в специализированных std::(io)fstream<> и std::(io)stringstream<>, причём, т.к. для них смена потоковых классов практически никогда не требуется, ещё и с собственными готовыми потомками std::basic_streambuf<>, уже инкапсулированными в форматирующий класс: std::basic_filebuf<> и std::basic_stringbuf<>. Но все они в целом не нужны, а присутствуют просто потому что stl озаботилась этим самым подавляющим большинством потребностей программистов. Заодно и добавив немножко специальных методов.
                              Так что тебе для работы с другими внешними хранилищами достаточно только реализовать свои потомки std::basic_streambuf<>, создавать по мере необходимости и передавать в основные форматирующие классы.

                              Добавлено
                              Цитата Eric-S @
                              На самом деле, мне надо сделать разные классы.
                              Там, предложенный вариант, тоже будет чудесно работать.
                              Но в итоге, работы получится слишком много.
                              Причём, однообразной и нудной.
                              Не увидел, где бы в предложенной схеме было много однообразной работы. Опять же, что значит "разные классы", что значит "разные места", почему нельзя дублирующую функциональность вынести в общие классы, чтобы не было "много однообразной работы", если уж она там в самом деле есть – всё это осталось за кадром.
                                Цитата Qraizer @
                                Не увидел, где бы в предложенной схеме было много однообразной работы. Опять же, что значит "разные классы", что значит "разные места", почему нельзя дублирующую функциональность вынести в общие классы, чтобы не было "много однообразной работы", если уж она там в самом деле есть – всё это осталось за кадром.

                                Ох... Ну ладно. Вот например:

                                ExpandedWrap disabled
                                  // тут у меня вся реализация. На самом деле почти так и сделано
                                  class file_impl abstract
                                  {
                                  public:
                                   
                                  file_impl():
                                  m_handle( null )
                                  {
                                  }
                                   
                                  ~file_impl()
                                  {
                                  if( null != m_handle )
                                  {
                                  close();
                                  }
                                  }
                                   
                                  void open_for_read( char* name )
                                  {
                                  m_handle = CreateFile( ... );
                                  }
                                   
                                  void open_for_write( char* name )
                                  {
                                  m_handle = CreateFile( ... );
                                  }
                                   
                                  void open_for_full( char* name )
                                  {
                                  m_handle = CreateFile( ... );
                                  }
                                   
                                  void close()
                                  {
                                  CloseHandle( m_handle );
                                  m_handle = null;
                                  }
                                   
                                  bool read( data_type& data )
                                  {
                                  ReadFile( m_handle, ... );
                                  }
                                   
                                  bool write( const data_type& data )
                                  {
                                  WriteFile( m_handle, ... );
                                  }
                                   
                                  private:
                                  HANDLE m_handle;
                                  };


                                Методов нужно больше, просто тут представлены все группы. Реализованы проверки ошибок и прочее и прочее.

                                Поехали, вот первый класс, реализации только для чтения.
                                ExpandedWrap disabled
                                  class file_reader final:
                                  public reader_base,
                                  protected file_impl
                                  {
                                  public:
                                   
                                  file_reader()
                                  {
                                  }
                                   
                                  ~file_reader()
                                  {
                                  }
                                   
                                  void open( char* name )
                                  {
                                  return file_impl::open_for_read( name );
                                  }
                                   
                                  void close()
                                  {
                                  return file_impl::close();
                                  }
                                   
                                  protected:
                                   
                                  bool do_read( data_type& data ) override final
                                  {
                                  return file_impl::read( data );
                                  }
                                   
                                  {;


                                Мне уже надоело набивать код, пусть он даже очень сильно упрощёный. Класс реализации записи.
                                ExpandedWrap disabled
                                  class file_writer final:
                                  public writer_base,
                                  protected file_impl
                                  {
                                  public:
                                   
                                  file_writer()
                                  {
                                  }
                                   
                                  ~file_writer()
                                  {
                                  }
                                   
                                  void open( char* name )
                                  {
                                  return file_impl::open_for_write( name );
                                  }
                                   
                                  void close()
                                  {
                                  return file_impl::close();
                                  }
                                   
                                  protected:
                                   
                                  bool do_write( const data_type& data ) override final
                                  {
                                  return file_impl::read( data );
                                  }
                                   
                                  {;



                                Реально задалбывает повторять, но сжав зубы, набиваю четвёртый класс.
                                ExpandedWrap disabled
                                  class file_full final:
                                  public full_base,
                                  protected file_impl
                                  {
                                  public:
                                   
                                  file_reader()
                                  {
                                  }
                                   
                                  ~file_reader()
                                  {
                                  }
                                   
                                  void open( char* name )
                                  {
                                  return file_impl::open_for_full( name );
                                  }
                                   
                                  void close()
                                  {
                                  return file_impl::close();
                                  }
                                   
                                  protected:
                                   
                                  bool do_read( data_type& data ) override final
                                  {
                                  return file_impl::read( data );
                                  }
                                   
                                  bool do_write( const data_type& data ) override final
                                  {
                                  return file_impl::read( data );
                                  }
                                   
                                  {;


                                И что, повторений нет?
                                Повторил, по сути, один и тот же класс, в четырёх разных вариациях.
                                А при этом, наверняка, ещё наделал ошибок.
                                Если же, как в реале, методов нужно больше, то работы больше и ошибок будет гораздо больше.

                                Ну ладно, класс file_impl у меня уже реализован. Мне всего лишь надо к нему сделать обёртку.
                                Но всё равно, эта обёртка повторится три раза.
                                Сообщение отредактировано: Eric-S -
                                  Цитата Eric-S @
                                  И что, повторений нет?
                                  Конечно есть. Ты же их набил. А зачем?
                                    Цитата Qraizer @
                                    Конечно есть. Ты же их набил. А зачем?

                                    Ну, вот. А в боевом коде, я не хочу повторений. И не желаю их набивать.

                                    Добавлено
                                    Цитата Qraizer @
                                    А зачем?

                                    Чтобы показать классы, показать повторение и всю хрень, от которой я хочу избавится.
                                      Так а кто ж тебя заставлял дублировать-то. Ты унаследовал метод, зачем его ещё раз определять и его вызов просто делегировать унаследованному? Зачем по-твоему методы наследуются-то?
                                        Цитата Qraizer @
                                        Ты унаследовал метод, зачем его ещё раз определять и его вызов просто делегировать унаследованному?

                                        Где я его унаследовал? Нету такого!
                                        Вот и приходится определять в дочернем классе.
                                        И да... на класс "file_impl" можно не обращать внимания, он вообще мифическое создание.
                                        У меня сейчас, по факту, один шаблонный клас, вместо четырёх "file_impl", "file_reader", "file_writer", "file_full".
                                        Сообщение отредактировано: Eric-S -
                                          :scratch: Ок.
                                          0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                          0 пользователей:


                                          Рейтинг@Mail.ru
                                          [ Script execution time: 0,1100 ]   [ 17 queries used ]   [ Generated: 29.03.24, 00:52 GMT ]