Spec-Zone.ru › C++

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

Спецификатор 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, и некоторый класс Derived, являющийся производным, прямо или косвенно, от Base, имеет объявление для член-функции с тем же

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

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

Base::vf не обязательно должна быть доступной или видимой для переопределения. (Base::vf может быть объявлена приватной, или 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).

Полезное руководство — деструктор любого базового класса должен быть публичным и виртуальным или защищённым и невиртуальным, когда задействованы выражения удаления, например, при неявном использовании в 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)
}

Отчёты о дефектах

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

DR Применено к Поведение, опубликованное Правильное поведение
CWG 258 C++98 непостоянная член-функция производного класса может стать
виртуальной из-за постоянной виртуальной член-функции её базы
виртуальность также требует, чтобы 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/w/cpp/language/virtual

Spec-Zone.ru

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