Реализация чисто виртуальной функции из абстрактного базового класса: имеет ли значение переопределение спецификатора?



Фон



Я только что наткнулся на случай использованияoverride спецификатор , который, насколько я могу судить, кажется избыточным и также без какого-либо конкретного семантического значения, но, возможно, я что-то упускаю, отсюда и этот вопрос. Прежде чем продолжить, я должен отметить, что я пытался найти ответ на него здесь на SO, но ближе всего я получил следующие темы, не совсем отвечающие на мой запрос (возможно, кто-то может указать вопрос и ответ, который на самом деле уже отвечает на мой вопрос). вопрос).




Вопрос



Рассмотрим следующий абстрактный класс:



struct Abstract {
virtual ~Abstract() {};
virtual void foo() = 0;
};


Есть ли какая-либо причина использовать спецификатор override при реализации foo() в неабстрактном классе, производном непосредственно от Abstract (как в DerivedB ниже)? Т. е., когда реализация foo() уже требуется для того, чтобы производный класс был не абстрактный (и не перекрывающий ничего)?

/* "common" derived class implementation, in my personal experience
(include virtual keyword for semantics) */
struct DerivedA : public Abstract {
virtual void foo() { std::cout << "A foo" << std::endl; }
};

/* is there any reason for having the override specifier here? */
struct DerivedB : public Abstract {
virtual void foo() override { std::cout << "B foo" << std::endl; }
};
478   4  
c++

4 ответов:

Я не большой поклонник override, но, предполагая, что это то, что вы находите полезным в целом, то да, ставить override на виртуальную функцию, которая переопределяет чисто виртуальные функции, полезно. Рассмотрим этот довольно надуманный пример:

struct Base {
    virtual void f() = 0;
};

struct Derived : Base {
    virtual void f();
    virtual void f(int);
};
Теперь предположим, что сопровождающий Base (возможно, даже ваше будущее "я") изменяет Base, чтобы выглядеть следующим образом:
struct Base {
    virtual void f(int) = 0;
};

Теперь поведение Derived тихо изменилось. С помощью override компилятор сообщит об ошибке.

С технической точки зрения обе версии являются синтаксически правильными и законными. Спецификатор override в основном предназначен для предотвращения непредвиденного поведения. Компилятор выдаст ошибку, как только он встретит функцию-член, помеченную как override, которая фактически не переопределяет виртуальную функцию. Это может произойти, если по какой-либо причине вы измените сигнатуру функции виртуального базового класса. Рассмотрим следующий пример:

class Abstract {
    virtual void foo() { ...}
}; 

class Derived : public Abstract {
    void foo() override { ... }
};

Теперь, если подпись Abstract::foo изменена, скажем to

class Abstract {
    virtual void foo(int bar) { ...}
}; 

Компилятор выдаст ошибку в Derived::foo, поскольку он больше не переопределяет функцию Abstract, что было бы невозможно без квалификатора override. Это поможет вам лучше поддерживать ваш код. Однако в вашем конкретном случае (т. е. с чисто виртуальными объявлениями) также будет выдана ошибка. Так что использование override в основном считается "хорошей практикой", я полагаю. Подробнее по этой теме: http://en.cppreference.com/w/cpp/language/override

В случае чисто виртуальных функций и компиляций не очень. Вы получите ошибку в любом случае (кроме как в Примере от Пита)

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

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

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

Что касается Почему хорошо, чтобы все переопределенные методы были объявлены override:

Представьте, что у вас есть

class A
{
    virtual void Foo();
};

class B: public A
{
    virtual void Foo() override;
};

И затем вы меняете Foo на const функцию в A. Без override это будет компилироваться, но когда вы вызываете A->foo() и это B объект B->foo() не будет вызван без каких-либо указаний что-то изменилось там. С override тобой получите ошибку здесь.

Главным преимуществом override здесь было бы поощрение ремонтопригодности. Рассмотрим ниже:

class Foo
{
public: 
    virtual void foo() = 0;
};
class Derived : public Foo
{
public:
    //....
    virtual void foo(double x) override
    {
         //This throws error
    }
};

Как вы можете видеть выше, компилятор выдаст ошибку, если вы скомпилировали выше. Компилятор будет жаловаться на то, что функция не имеет такой же сигнатуры. Без ключевого слова override результат был бы иным.

Comments

    Ничего не найдено.