[ Note: Виртуальные функции поддерживают динамическое связывание и объектно-ориентированное программирование. ] Класс, который объявляет или наследует виртуальную функцию, называется a . — end note polymorphic class
Если виртуальная функция-членvf объявлена в классе Base и в классеDerived, производная прямо или косвенно отBaseфункции-членаvf с тем же именемparameter-type-list,, cv-qualification и ref-qualifier (или отсутствием того же), что Base::vf и объявлено, тогдаDerived::vf is также виртуальный (объявлен ли он так или нет) и онoverrides111 Base::vf. Для удобства мы говорим, что любая виртуальная функция отменяет сама себя. Виртуальная функция - членC::vf объекта классаS является ,final overrider еслиmost derived class из которыхS не является базовым классом подобъектом (если таковые имеются) объявляет или наследует другую функцию - член , который переопределяет vf. В производном классе, если виртуальная функция-член подобъекта базового класса имеет более одного окончательного переопределителя, программа имеет неправильный формат. [ Example:
struct A { virtual void f(); }; struct B : virtual A { virtual void f(); }; struct C : B , virtual A { using A::f; }; void foo() { C c; c.f(); // calls B::f, the final overrider c.C::f(); // calls A::f because of the using-declaration }
— end example ]
[ Example:
struct A { virtual void f(); }; struct B : A { }; struct C : A { void f(); }; struct D : B, C { }; // OK: A::f and C::f are the final overriders // for the B and C subobjects, respectively
— end example ]
[ Note: Виртуальная функция-член не должна быть видимой для переопределения, например,
struct B { virtual void f(); }; struct D : B { void f(int); }; struct D2 : D { void f(); };
функцияf(int) в классеD скрывает виртуальную функциюf() в своем базовом классеB;D::f(int) это не виртуальная функция. Однакоf() объявленный в классе D2 имеет то же имя и тот же список параметров B::f(), что и, и, следовательно, является виртуальной функцией, которая переопределяет функцию,B::f() даже еслиB::f() она не видна в классеD2. ] — end note
Если виртуальная функцияf в каком-либо классеB отмечена значками virt-specifierfinal и в классе,D производном от переопределенияB функции , программа имеет неправильный формат. [D::f B::f Example:
struct B {
virtual void f() const final;
};
struct D : B {
void f() const; // error: D::f attempts to override final B::f
};
— end example ]
Если виртуальная функция отмечена знаком virt-specifieroverride и не переопределяет функцию-член базового класса, программа имеет неправильный формат. [ Example:
struct B { virtual void f(int); }; struct D : B { virtual void f(long) override; // error: wrong signature overriding B::f virtual void f(int) override; // OK };
— end example ]
Несмотря на то, что деструкторы не наследуются, деструктор в производном классе переопределяет деструктор базового класса, объявленный виртуальным; видеть[class.dtor] и[class.free].
Тип возврата замещающей функции должен быть идентичен типу возврата замещенной функции илиcovariant классам функций. Если функцияD::f переопределяет функциюB::f, возвращаемые типы функций являются ковариантными, если они удовлетворяют следующим критериям:
оба являются указателями на классы, оба являются ссылками lvalue на классы или оба являются ссылками rvalue на классы112
класс в возвращаемом типеB::f является тем же классом, что и класс в возвращаемом типеD::f, или является однозначным и доступным прямым или косвенным базовым классом класса в возвращаемом типеD::f
оба указателя или ссылки имеют одинаковую квалификацию cv, а тип класса в типе возвращаемого значенияD::f имеет такую же квалификацию CV или меньшую квалификацию, чем тип класса в типе возвращаемого значенияB::f.
Если тип класса в ковариантном возвращаемом типеD::f отличается от типа B::fкласса, тип класса в возвращаемом типеD::f должен быть полным в момент объявленияD::f или должен быть типом классаD. Когда переопределяющая функция вызывается как конечный переопределитель переопределенной функции, ее результат преобразуется в тип, возвращаемый (статически выбранной) переопределенной функцией ([expr.call]). [ Example:
class B { }; class D : private B { friend class Derived; }; struct Base { virtual void vf1(); virtual void vf2(); virtual void vf3(); virtual B* vf4(); virtual B* vf5(); void f(); }; struct No_good : public Base { D* vf4(); // error: B (base class of D) inaccessible }; class A; struct Derived : public Base { void vf1(); // virtual and overrides Base::vf1() void vf2(int); // not virtual, hides Base::vf2() char vf3(); // error: invalid difference in return type only D* vf4(); // OK: returns pointer to derived class A* vf5(); // error: returns pointer to incomplete class void f(); }; void g() { Derived d; Base* bp = &d; // standard conversion: // Derived* to Base* bp->vf1(); // calls Derived::vf1() bp->vf2(); // calls Base::vf2() bp->f(); // calls Base::f() (not virtual) B* p = bp->vf4(); // calls Derived::pf() and converts the // result to B* Derived* dp = &d; D* q = dp->vf4(); // calls Derived::pf() and does not // convert the result to B* dp->vf2(); // ill-formed: argument mismatch }
— end example ]
[ Note: Интерпретация вызова виртуальной функции зависит от типа объекта, для которого она вызывается (динамический тип), тогда как интерпретация вызова невиртуальной функции-члена зависит только от типа указателя или ссылка, обозначающая этот объект (статический тип) ([expr.call]). ] — end note
[ Note: Спецификаторvirtual подразумевает членство, поэтому виртуальная функция не может быть функцией, не являющейся членом ([dcl.fct.spec]). Виртуальная функция также не может быть статическим членом, поскольку вызов виртуальной функции полагается на конкретный объект для определения того, какую функцию вызывать. Виртуальная функция, объявленная в одном классе, может быть объявленаfriend в другом классе. ] — end note
Виртуальная функция, объявленная в классе, должна быть определена или объявлена pure в этом классе, либо в том и другом; диагностика не требуется ([basic.def.odr]).
[ Example: Вот некоторые варианты использования виртуальных функций с несколькими базовыми классами:
struct A { virtual void f(); }; struct B1 : A { // note non-virtual derivation void f(); }; struct B2 : A { void f(); }; struct D : B1, B2 { // D has two separate A subobjects }; void foo() { D d; // A* ap = &d; // would be ill-formed: ambiguous B1* b1p = &d; A* ap = b1p; D* dp = &d; ap->f(); // calls D::B1::f dp->f(); // ill-formed: ambiguous }
В классеD выше есть два вхождения классаA и, следовательно, два вхождения виртуальной функции-членаA::f. Последний приоритетB1::A::f - этоB1::f и последний приоритетB2::A::f - этоB2::f. ] — end example
[ Example: В следующем примере показана функция, не имеющая уникального окончательного переопределителя:
struct A { virtual void f(); }; struct VB1 : virtual A { // note virtual derivation void f(); }; struct VB2 : virtual A { void f(); }; struct Error : VB1, VB2 { // ill-formed }; struct Okay : VB1, VB2 { void f(); };
ОбаVB1::f иVB2::f переопределяют,A::f но в классе нет их переопределенияError. Следовательно, этот пример неверен. Однако классOkay хорошо сформирован, потому чтоOkay::f он окончательный приоритет. ] — end example
[ Example: В следующем примере используются хорошо сформированные классы, указанные выше.
struct VB1a : virtual A { // does not declare f }; struct Da : VB1a, VB2 { }; void foe() { VB1a* vb1ap = new Da; vb1ap->f(); // calls VB2::f }
— end example ]
Явная квалификация с помощью оператора области видимости ([expr.prim]) подавляет механизм виртуального вызова. [ Example:
class B { public: virtual void f(); };
class D : public B { public: void f(); };
void D::f() { /* ... */ B::f(); }
Здесь вызов функции D::f действительно вызывает,B::f а не вызывает D::f. ] — end example
Функция с удаленным определением ([dcl.fct.def]) не должна переопределять функцию, у которой нет удаленного определения. Точно так же функция, у которой нет удаленного определения, не должна переопределять функцию с удаленным определением.
Функция с тем же именем, но с другим списком параметров (Clause[over]) в качестве виртуальной функции не обязательно является виртуальной и не отменяет. Использованиеvirtual спецификатора в объявлении замещающей функции допустимо, но избыточно (имеет пустую семантику).Access control не учитывается при определении приоритета.
Многоуровневые указатели на классы или ссылки на многоуровневые указатели на классы не допускаются.