C++ "виртуальное" ключевое слово для функций в производных классах. Это необходимо?



с определением структуры, приведенным ниже...



struct A {
virtual void hello() = 0;
};


подход #1:



struct B : public A {
virtual void hello() { ... }
};


подход #2:



struct B : public A {
void hello() { ... }
};


есть ли разница между этими двумя способами переопределения функции hello?

631   8  

8 ответов:

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

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

С чисто стилистической точки зрения, в том числе virtual ключевое слово явно "рекламирует" тот факт, что функция является виртуальной. Это будет важно для любого дальнейшего подкласса B без необходимости проверять определение A. Для глубоких иерархий классов, это становится особенно важно.

The virtual ключевое слово не нужно в производном классе. Вот сопроводительная документация, из проекта стандарта C++ (N3337) (акцент мой):

виртуальные функции 10.3

2 если виртуальная функция-член vf объявляется в классе Base и Derived, полученной прямо или косвенно от Base функции-члена vf с тем же именем, параметром-тип-список (8.3.5), cv-квалификация, и ref-квалификатор (или отсутствие такового) как Base::vf объявляется, то Derived::vf также является виртуальным (так ли это объявлено) и он переопределяет Base::vf.

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

The не удалось переопределить происходит, если вы собираетесь переопределить виртуальную функцию в производном классе, но сделать ошибку в сигнатуре, чтобы она объявила новую и другую виртуальную функцию. Эта функция может быть перегрузка функции базового класса, или его может отличаться по названию. Независимо от того, используете ли вы virtual ключевое слово в объявлении функции производного класса компилятор не сможет определить, что вы собираетесь переопределить функцию из базового класса.

эта ловушка, однако, к счастью, рассматривается C++11 явное переопределение функция языка, которая позволяет исходному коду четко указать, что функция-член предназначена для переопределения функции базового класса:

struct Base {
    virtual void some_func(float);
};

struct Derived : Base {
    virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};

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

смотрите WP:C++11.

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

нет никакой разницы для компилятора, когда вы пишите virtual в производном классе или опустить ее.

но нужно смотреть на базовый класс, чтобы получить эту информацию. Поэтому я бы рекомендовал добавить virtual ключевое слово также в производном классе, если вы хотите показать человеку, что эта функция виртуальна.

есть большая разница, когда у вас есть шаблоны и начать принимать базовый класс(ы) в качестве параметра шаблона(ы):

struct None {};

template<typename... Interfaces>
struct B : public Interfaces
{
    void hello() { ... }
};

struct A {
    virtual void hello() = 0;
};

template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
    b.hello();   // indirect, non-virtual call
}

void hello(const A& a)
{
    a.hello();   // Indirect virtual call, inlining is impossible in general
}

int main()
{
    B<None>  b;         // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
    B<None>* pb = &b;
    B<None>& rb = b;

    b.hello();          // direct call
    pb->hello();        // pb-relative non-virtual call (1 redirection)
    rb->hello();        // non-virtual call (1 redirection unless optimized out)
    t_hello(b);         // works as expected, one redirection
    // hello(b);        // compile-time error


    B<A>     ba;        // Ok, vtable generated, sizeof(b) >= sizeof(void*)
    B<None>* pba = &ba;
    B<None>& rba = ba;

    ba.hello();         // still can be a direct call, exact type of ba is deducible
    pba->hello();       // pba-relative virtual call (usually 3 redirections)
    rba->hello();       // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
    //t_hello(b);       // compile-time error (unless you add support for const A& in t_hello as well)
    hello(ba);
}

самое интересное, что теперь вы можете определить интерфейсные и неинтерфейсные функции позже в определении классов. Это полезно для взаимодействия интерфейсов между библиотеками (не полагайтесь на это как на стандартный процесс проектирования a один библиотека). Вам ничего не стоит разрешить это для всех ваших классов - вы можете даже typedef B к чему-то, если вы хотите.

обратите внимание, что, если вы это сделаете, вы также можете объявить конструкторы копирования / перемещения в качестве шаблонов: разрешение создавать из разных интерфейсов позволяет вам "бросать" между разными B<> типы.

сомнительно, следует ли добавлять поддержку для const A& на t_hello(). Обычная причина для этой перезаписи-это переход от специализации на основе наследования к специализации на основе шаблонов, в основном по соображениям производительности. Если вы продолжаете поддерживать старый интерфейс, вы вряд ли можете обнаружить (или препятствуют) использование.

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

Comments

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