Подтвердить что ты не робот

Варианты использования для финальных классов

Я читал комментарии в книге Herb Sutter Guru of the week о функциях virtual и, наконец, заметил, что он упоминает об этом:

[...] "использование финала реже" - ну, они вроде бы. Я не знаю многих, и во время стандартизации Бьярн неоднократно просил примеры проблем, которые он решил, и шаблоны, где он должен использоваться, и я не помню каких-либо крупных, которые выделялись. Единственный, кого я знаю из-за того, что если вы определяете библиотечный модуль (который еще не является стандартным понятием), то окончание листовых классов может дать компилятору больше информации для девиртуализации вызовов из-за знания кода вне библиотеки, а не последующего вывода, но Im не уверен, насколько важно это в наши дни при всей оптимизации программы, включая агрессивную девиртуализацию.

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

4b9b3361

Ответ 1

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

Но более распространенным примером является девиртуализация. Если вы отметите свой класс как final, компилятор может применить определенные оптимизации во время выполнения. Например,

struct Object {
  virtual void run() = 0;
  virtual ~Object() {}
};

struct Impl final : Object
{
  void run() override {}
};

void fun(Impl & i)
{
  i.run(); // inlined!
}

Вызов i.run() может быть теперь привязан по спецификатору final. Компилятор знает, что vtable look-up не понадобится.

Ответ 2

final может быть полезна, когда вы предоставляете (вид) фасада исходному интерфейсу, который проще использовать подклассами. Рассмотрим:

class IMovable {
  public:
    void GoTo(unsigned position) = 0;
}

class Stepper : public IMovable {
  public:
    void GoTo(unsigned position) final;
  protected:
    virtual void MoveLeft() = 0;
    virtual void MoveRight() = 0;
}

void Stepper::GoTo(unsigned position) {
  for(;current_pos < position; current_pos++) {
     MoveRight(); 
  }
  for(;current_pos > position; current_pos--) {
     MoveLeft();
  } 
}       

Теперь, если вы хотите получить Stepper, вы увидите, что вы должны переопределить MoveRight и MoveLeft, но вы не должны переопределять GoTo.

Это очевидно на этом маленьком примере, но если IMovable было 20 методов, а у Stepper было 25, и были реализации по умолчанию, чем вам может быть трудно определить, что вы должны и что вы не должны переопределять. Я встречал такую ​​ситуацию в библиотеке, связанной с оборудованием. Но я бы не назвал это серьезной проблемой, заслуживающей внимания стандартом;)