Spec-Zone.ru › C++

virtual спецификатор функции

Указывает, что нестатическая функция-член является виртуальной и поддерживает динамическое связывание. Он может присутствовать только в decl-specifier-seq первоначального объявления нестатической функции-члена (то есть при её объявлении в определении класса).

Объяснение

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

#include <iostream>

struct Base
{
    virtual void f()
    {
        std::cout << "base\n";
    }
};

struct Derived : Base
{
    void f() override // 'override' is optional
    {
        std::cout << "derived\n";
    }
};

int main()
{
    Base b;
    Derived d;

    // virtual function call through reference
    Base& br = b; // the type of br is Base&
    Base& dr = d; // the type of dr is Base& as well
    br.f(); // prints "base"
    dr.f(); // prints "derived"

    // virtual function call through pointer
    Base* bp = &b; // the type of bp is Base*
    Base* dp = &d; // the type of dp is Base* as well
    bp->f(); // prints "base"
    dp->f(); // prints "derived"

    // non-virtual function call
    br.Base::f(); // prints "base"
    dr.Base::f(); // prints "base"
}

Подробнее

Если некоторая функция-член vf объявлена как virtual в классе Base, а некоторый производный прямо или косвенно от Base класс Derived объявляет функцию-член с теми же

  • именем
  • списком типов параметров (но не типом возвращаемого значения)
  • cv-квалификаторами
  • ref-квалификаторами

Тогда эта функция в классе Derived также является виртуальной (независимо от того, используется ли ключевое слово virtual в её объявлении) и переопределяет Base::vf (независимо от того, используется ли спецификатор override в её объявлении).

Base::vf не обязана быть доступной или видимой, чтобы её можно было переопределить. (Base::vf можно объявить как private, а Base можно унаследовать с помощью закрытого наследования. Наличие членов с тем же именем в базовом классе Derived, который наследует Base, не влияет на определение переопределения, даже если такие члены скрывали бы Base::vf при поиске имени.)

class B
{
    virtual void do_f(); // private member
public:
    void f() { do_f(); } // public interface
};

struct D : public B
{
    void do_f() override; // overrides B::do_f
};

int main()
{
    D d;
    B* bp = &d;
    bp->f(); // internally calls D::do_f();
}

Для каждой виртуальной функции существует окончательный переопределитель — функция, которая выполняется при вызове виртуальной функции. Виртуальная функция-член vf базового класса Base является окончательным переопределителем, если только производный класс не объявляет и не наследует (через множественное наследование) другую функцию, переопределяющую vf.

struct A { virtual void f(); };     // A::f is virtual
struct B : A { void f(); };         // B::f overrides A::f in B
struct C : virtual B { void f(); }; // C::f overrides A::f in C

struct D : virtual B {}; // D does not introduce an overrider, B::f is final in D

struct E : C, D          // E does not introduce an overrider, C::f is final in E
{
    using A::f; // not a function declaration, just makes A::f visible to lookup
};

int main()
{
    E e;
    e.f();    // virtual call calls C::f, the final overrider in e
    e.E::f(); // non-virtual call calls A::f, which is visible in E
}

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

struct A
{
    virtual void f();
};

struct VB1 : virtual A
{
    void f(); // overrides A::f
};

struct VB2 : virtual A
{
    void f(); // overrides A::f
};

// struct Error : VB1, VB2
// {
//     // Error: A::f has two final overriders in Error
// };

struct Okay : VB1, VB2
{
    void f(); // OK: this is the final overrider for A::f
};

struct VB1a : virtual A {}; // does not declare an overrider

struct Da : VB1a, VB2
{
    // in Da, the final overrider of A::f is VB2::f
};

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

struct B
{
    virtual void f();
};

struct D : B
{
    void f(int); // D::f hides B::f (wrong parameter list)
};

struct D2 : D
{
    void f(); // D2::f overrides B::f (doesn't matter that it's not visible)
};

int main()
{
    B b;
    B& b_as_b = b;
    
    D d;
    B& d_as_b = d;
    D& d_as_d = d;
    
    D2 d2;
    B& d2_as_b = d2;
    D& d2_as_d = d2;
    
    b_as_b.f();  // calls B::f()
    d_as_b.f();  // calls B::f()
    d2_as_b.f(); // calls D2::f()
    
    d_as_d.f();  // Error: lookup in D finds only f(int)
    d2_as_d.f(); // Error: lookup in D finds only f(int)
}

Если функция объявлена со спецификатором override, но не переопределяет виртуальную функцию, программа некорректна:

struct B
{
    virtual void f(int);
};

struct D : B
{
    virtual void f(int) override;  // OK, D::f(int) overrides B::f(int)
    virtual void f(long) override; // Error: f(long) does not override B::f(int)
};

Если функция объявлена со спецификатором final, а другая функция пытается её переопределить, программа некорректна:

struct B
{
    virtual void f() const final;
};

struct D : B
{
    void f() const; // Error: D::f attempts to override final B::f
};
(начиная с C++11)

Функции, не являющиеся членами класса, и статические функции-члены не могут быть виртуальными.

Шаблоны функций нельзя объявлять как virtual. Это относится только к функциям, которые сами являются шаблонами: обычная функция-член шаблона класса может быть объявлена виртуальной.

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

struct A
{
    virtual void f() requires true; // Error: constrained virtual function
};

Виртуальная функция consteval не должна переопределять невиртуальную функцию consteval или переопределяться такой функцией.

(начиная с C++20)

Аргументы по умолчанию для виртуальных функций подставляются во время компиляции.

Ковариантные типы возвращаемых значений

Если функция Derived::f переопределяет функцию Base::f, их типы возвращаемых значений должны совпадать или быть ковариантными. Два типа являются ковариантными, если выполняются все следующие требования:

  • оба типа являются указателями или ссылками (lvalue или rvalue) на классы. Указатели и ссылки на несколько уровней не допускаются.
  • класс, на который указывает ссылка или указатель в типе возвращаемого значения Base::f(), должен быть однозначным и доступным прямым или косвенным базовым классом класса, на который указывает ссылка или указатель в типе возвращаемого значения Derived::f().
  • тип возвращаемого значения Derived::f() должен иметь такую же или меньшую cv-квалификацию, чем тип возвращаемого значения Base::f().

Класс в типе возвращаемого значения Derived::f должен быть либо самим Derived, либо полным типом в точке объявления Derived::f.

При вызове виртуальной функции тип, возвращаемый окончательным переопределителем, неявно преобразуется к типу возвращаемого значения переопределённой функции, которая была вызвана:

class B {};

struct Base
{
    virtual void vf1();
    virtual void vf2();
    virtual void vf3();
    virtual B* vf4();
    virtual B* vf5();
};

class D : private B
{
    friend struct Derived; // in Derived, B is an accessible base of D
};

class A; // forward-declared class is an incomplete type

struct Derived : public Base
{
    void vf1();    // virtual, overrides Base::vf1()
    void vf2(int); // non-virtual, hides Base::vf2()
//  char vf3();    // Error: overrides Base::vf3, but has different
                   // and non-covariant return type
    D* vf4();      // overrides Base::vf4() and has covariant return type
//  A* vf5();      // Error: A is incomplete type
};

int main()
{
    Derived d;
    Base& br = d;
    Derived& dr = d;

    br.vf1(); // calls Derived::vf1()
    br.vf2(); // calls Base::vf2()
//  dr.vf2(); // Error: vf2(int) hides vf2()

    B* p = br.vf4(); // calls Derived::vf4() and converts the result to B*
    D* q = dr.vf4(); // calls Derived::vf4() and does not convert the result to B*
}

Виртуальный деструктор

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

class Base
{
public:
    virtual ~Base() { /* releases Base's resources */ }
};

class Derived : public Base
{
    ~Derived() { /* releases Derived's resources */ }
};

int main()
{
    Base* b = new Derived;
    delete b; // Makes a virtual function call to Base::~Base()
              // since it is virtual, it calls Derived::~Derived() which can
              // release resources of the derived class, and then calls
              // Base::~Base() following the usual order of destruction
}

Кроме того, если деструктор базового класса не является виртуальным, удаление объекта производного класса через указатель на базовый класс приводит к неопределённому поведению независимо от того, будут ли утекать ресурсы, если деструктор производного класса не будет вызван, за исключением случая, когда выбранная функция освобождения памяти является разрушающей operator delete(начиная с C++20).

Полезное правило: деструктор любого базового класса должен быть открытым и виртуальным либо защищённым и невиртуальным, если используются выражения delete, например при неявном использовании в std::unique_ptr(начиная с C++11).

Во время конструирования и разрушения

Если виртуальная функция вызывается прямо или косвенно из конструктора или деструктора (в том числе во время конструирования или разрушения нестатических членов данных класса, например, в списке инициализации члена), а объект, к которому относится вызов, является конструируемым или разрушаемым объектом, вызывается окончательный переопределитель в классе конструктора или деструктора, а не функция, переопределяющая его в более производном классе. Иными словами, во время конструирования или разрушения более производные классы не существуют.

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

struct V
{
    virtual void f();
    virtual void g();
};

struct A : virtual V
{
    virtual void f(); // A::f is the final overrider of V::f in A
};

struct B : virtual V
{
    virtual void g(); // B::g is the final overrider of V::g in B
    B(V*, A*);
};

struct D : A, B
{
    virtual void f(); // D::f is the final overrider of V::f in D
    virtual void g(); // D::g is the final overrider of V::g in D

    // note: A is initialized before B
    D() : B((A*) this, this) {}
};

// the constructor of B, called from the constructor of D 
B::B(V* v, A* a)
{
    f(); // virtual call to V::f (although D has the final overrider, D doesn't exist)
    g(); // virtual call to B::g, which is the final overrider in B 

    v->g(); // v's type V is base of B, virtual call calls B::g as before

    a->f(); // a’s type A is not a base of B. it belongs to a different branch of the
            // hierarchy. Attempting a virtual call through that branch causes
            // undefined behavior even though A was already fully constructed in this
            // case (it was constructed before B since it appears before B in the list
            // of the bases of D). In practice, the virtual call to A::f will be
            // attempted using B's virtual member function table, since that's what
            // is active during B's construction)
}

Ключевые слова

virtual

Отчёты об ошибках стандарта

Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.

DR Применяется к Поведение согласно опубликованной версии Исправленное поведение
CWG 258 C++98 нестатическая функция-член производного класса без const могла стать
виртуальной из-за виртуальной функции-члена базового класса с const
для виртуальности также требуется совпадение cv-
квалификаторов
CWG 477 C++98 объявление друга могло содержать спецификатор virtual не допускается
CWG 1516 C++98 определения терминов «вызов виртуальной функции»
и «виртуальный вызов» отсутствовали
добавлены

См. также

производные классы и режимы наследования
спецификатор override (C++11) явно указывает, что метод переопределяет другой метод
спецификатор final (C++11) указывает, что метод нельзя переопределить или от класса нельзя наследоваться

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/virtual

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API