На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> CRTP inheritance problem
    Есть базовый шаблонный класс и его шаблонные наследники:
    ExpandedWrap disabled
      template<class Derived, class Measure>
      class IntervalMeasurer
      {
      protected:
          std::vector<Measure> measures;
          uint32_t interval = 0;
      public:
          void setMeasureInterval(uint32_t measure_interval) {
              this->interval = measure_interval;
              this->measures.reserve(uint32_t(max_period) / measure_interval);
          }
          void addMeasure(Measure value) {
              if (measures.size() == measures.capacity()) {
                  measures.erase(measures.begin());
              }
              measures.push_back(value);
          }
          Measure getAvg1() const {
              return static_cast<Derived*>(this)->getAverage(1*60);
          }
          Measure getAvg5() const {
              return static_cast<Derived*>(this)->getAverage(5*60);
          }
          Measure getAvg15() const {
              return static_cast<Derived*>(this)->getAverage(15*60);
          }
      };
       
      template<class Measure>
      class IntervalInstantMeasurer : public IntervalMeasurer<IntervalInstantMeasurer<Measure>, Measure>
      {
      public:
          Measure getAverage(uint32_t period) const {
              uint32_t count = std::min(period/interval, measures.size());
              Measure sum = std::accumulate(measures.rbegin(), measures.rbegin()+(count-1), Measure());
              return sum / count;
          }
      };
       
      template<class Measure>
      class IntervalGrowingMeasurer : public IntervalMeasurer<IntervalGrowingMeasurer<Measure>, Measure>
      {
      public:
          Measure getAverage(uint32_t period) const {
              uint32_t count = std::min(period/interval, measures.size());
              if (count >= 2) {
                  Measure current_measure = measures.back();
                  Measure previous_measure = measures[measures.size() - count];
                  return (current_measure - previous_measure) / period;
              }
              return Measure();
          }
      };


    Почему компилятор выдает ошибки рода:
    ExpandedWrap disabled
      error: ‘interval’ was not declared in this scope
      error: ‘measures’ was not declared in this scope

    ? :wall:
    Сообщение отредактировано: AZote -
      Надо явно указывать базовый класс:
      ExpandedWrap disabled
        Measure current_measure = IntervalMeasurer<IntervalGrowingMeasurer<Measure>, Measure>::measures.back();
        Цитата Dushevny @
        Надо явно указывать базовый класс:

        А знаете что, с this прокатило
        ExpandedWrap disabled
              Measure getAverage(uint32_t period) const {
                  uint32_t count = std::min(size_t(period/this->interval), this->measures.size());
                  Measure sum = std::accumulate(this->measures.rbegin(), this->measures.rbegin()+(count-1), Measure());
                  return sum / count;
              }

        :blink:
          Могу предположить что когда он доходит до компиляции твоих пременных, он просто про них не знает, так как класс базовый у тебя шаблонный, и он судя во всему до этого момента еще не инстанцирован. Поэтому и ругнулся.
          А то что ты написал там this и у тебя заработало, так это называеться Two phase lookup

          Добавлено
          Вот тут еще про это даже посмотреть можно:
          https://www.youtube.com/watch?v=LZPHrYZ_dBw

          Добавлено
          Тут по русски можно почитать кусочек: https://books.google.ru/books?id=Gn11DwAAQB...1%D0%BA&f=false
            Цитата Wound @
            Могу предположить что когда он доходит до компиляции твоих пременных, он просто про них не знает, так как класс базовый у тебя шаблонный, и он судя во всему до этого момента еще не инстанцирован.
            Не, не поэтому. Был или не был инстанцирован, неважно, это уже вторая фаза, и ежели чё, тут же и сынстанцируется. Ошибка же возникает ещё на первой фазе связывания имён. И interval, и measures не обозначены как зависимые имена, т.к. для них отсутствует квалификация. По правилам связывания имён независимые имена обязаны быть окончательно связаны на первой фазе, в противном случае велик риск получить сайд-эффекты а-ля препроцессор. Т.с. компилятор защищает автора базового шаблона, который отсутствием квалификации для независимых имён зафиксировал их смысл, от неожиданного контекста в точке инстацирования, который пользователь авторского шаблона невольно мог наполнить "неподходящими" именами (на чём макросоподобный сайд-эффект как раз и проявился бы).
            Поэтому компилятор первым делом смотрит в текущую область видимости, не видит их и смотрит в окаймляющую, каковой является глобальная. В область видимости базового класса, даже притом, что он определён, он не заглядывает, т.к. у него нет никакой информации, что туда надо смотреть, независимые имена там точно не должны быть найдены. Кроме того, он и не должен туда смотреть, т.к. в точке инстацирования к первичному шаблону базового класса могут быть добавлены специализации (размещённые уже после определения производных классов, такой же сайд-эффект в общем-то), поэтому, если б он туда смотрел, собранная на первой фазе информация об именах может быть не полной, искажённой сайд-эффектами или даже ошибочной.
            Исправление ситуации достигается введением зависимости для interval и measures посредством явной квалификации. И это правильно, т.к. т.о. пользователь шаблона явно декларирует их зависимость от шаблонных параметров, что даёт право компилятору отложить их окончательное связывание до точки инстанцирования. Можно квалифицирующим маршрутом явно указать базовый класс, как у Dushevny, можно и просто посредством this, который неявно подразумевает полную область видимости инстанцируемого экземпляра, включая и унаследованные сущности. В обоих случаях эти имена оказываются явно зависимыми от контента базового класса, а значит их окончательное связывание должно быть отложено до второй фазы. Что компилятор и делает, и у него всё срастается, т.к. это именно то, что хотел пользователь шаблона.

            Добавлено
            По первой ссылке Wound как раз приводится хороший пример сайд-эффекта.
            Сообщение отредактировано: Qraizer -
              Кстати, не подскажете, что кошернее, использовать CRTP, как я сделал выше, или сделать виртуальную функцию в шаблонном классе, например так:

              ExpandedWrap disabled
                template<class Measure>
                class IntervalMeasurer
                {
                protected:
                    std::vector<Measure> measures;
                    uint32_t interval = 0;
                public:
                    void addMeasure(Measure value) {
                        measures.push_back(measure);
                    }
                    virtual Measure getAverage(uint32_t period) const = 0;
                 
                    Measure getAvg1() const {
                        return getAverage(1*60);
                    }
                    Measure getAvg5() const {
                        return getAverage(5*60);
                    }
                };
                 
                template<class Measure>
                class InstantIntervalMeasurer : public IntervalMeasurer<Measure>
                {
                public:
                    Measure getAverage(uint32_t period) const override {
                        return Measure();
                    }
                };
                Цитата AZote @
                .. компилятор выдает ошибки рода:
                ExpandedWrap disabled
                  error: ‘interval’ was not declared in this scope
                  error: ‘measures’ was not declared in this scope

                ? :wall:

                А если попробовать приблизительно так:
                ExpandedWrap disabled
                  template<class Measure>
                  class IntervalInstantMeasurer : public IntervalMeasurer<IntervalInstantMeasurer<Measure>, Measure>
                  {
                   
                  protected:
                      using IntervalMeasurer<IntervalInstantMeasurer<Measure>, Measure>::std::vector<Measure> measures;
                      using IntervalMeasurer<IntervalInstantMeasurer<Measure>, Measure>::uint32_t interval;
                   
                  public:
                   
                  // ...
                  // ...
                   
                  };
                Сообщение отредактировано: ЫукпШ -
                  Цитата AZote @
                  Кстати, не подскажете, что кошернее, использовать CRTP, как я сделал выше, или сделать виртуальную функцию в шаблонном классе, например так:

                  Виртуальная функция нужна если ты планируешь делать так

                  ExpandedWrap disabled
                    IntervalMeasurer<Measure> *p = new InstantIntervalMeasurer<Measure>();
                    p->getAverage(...);


                  Тогда неплохо было бы добавить ещё и вертуальный деструктор.

                  Если не планируешь, то лучше без виртульных методов
                    Цитата AZote @
                    Кстати, не подскажете, что кошернее, использовать CRTP, как я сделал выше, или сделать виртуальную функцию в шаблонном классе... ?
                    Стандартный полиморфизм выгоден, когда реализация задаётся, а то и меняется, т.е. не фиксирована, в ран-тайм. Если такой задачи нет, то статический полиморфизм на шаблонах выгоднее. Ты по-прежнему имеешь возможность легко и просто менять реализацию, только не в ран-тайм, а в перед компиляцией, и при этом не имеешь потерь в производительности от позднего связывания.
                    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                    0 пользователей:


                    Рейтинг@Mail.ru
                    [ Script execution time: 0,0460 ]   [ 17 queries used ]   [ Generated: 19.03.24, 09:04 GMT ]