13 Derived classes [class.derived]

Список базовых классов можно указать в определении класса, используя обозначение:

base-clause:
	: base-specifier-list
base-specifier-list:
	base-specifier ...opt
	base-specifier-list , base-specifier ...opt
base-specifier:
	attribute-specifier-seqopt class-or-decltype
	attribute-specifier-seqopt virtual access-specifieropt class-or-decltype
	attribute-specifier-seqopt access-specifier virtualopt class-or-decltype
class-or-decltype:
	nested-name-specifieropt class-name
	nested-name-specifier template simple-template-id
	decltype-specifier
access-specifier:
	private
	protected
	public

Необязательный attribute-specifier-seqэлемент принадлежит к base-specifier.

A class-or-decltypeдолжен обозначать тип класса, который не является полностью определенным классом (пункт [class]). Класс, обозначенный символом class-or-decltypea base-specifier, называется a direct base class для определяемого класса. Во время поиска имени базового класса имена, не являющиеся типами, игнорируются ([basic.scope.hiding]). Если найденное имя не a class-name, программа имеет неправильный формат. Класс B - это базовый класс класса, D если он является прямым базовым классом D или прямым базовым классом одного из Dбазовых классов компании. Класс является indirect базовым классом другого, если это базовый класс, но не прямой базовый класс. Говорят, что класс (прямо или косвенно) принадлежит к derived своим (прямым или косвенным) базовым классам. [ Note: См. Пункт [class.access] о значении access-specifier. ] Если не объявлено повторно в производном классе, члены базового класса также считаются членами производного класса. Члены базового класса, отличные от конструкторов, называются производным классом. Конструкторы базового класса также могут быть унаследованы, как описано в . Унаследованные члены могут упоминаться в выражениях так же, как и другие члены производного класса, если их имена не являются скрытыми или неоднозначными ( ). [ Оператор разрешения области видимости ( ) может использоваться для явной ссылки на прямой или косвенный базовый член. Это позволяет получить доступ к имени, которое было повторно объявлено в производном классе. Производный класс может сам служить базовым классом, подлежащим контролю доступа; см . Указатель на производный класс может быть неявно преобразован в указатель на доступный однозначный базовый класс ( ). Значение lvalue типа производного класса может быть связано со ссылкой на доступный однозначный базовый класс ( ). ]end noteinherited [namespace.udecl][class.member.lookup]Note: ​::​ [expr.prim] [class.access.base][conv.ptr][dcl.init.ref]end note

В base-specifier-listопределяете типа ,base class subobjects содержащийся в объекте производного типа класса. [Example:

struct Base {
  int a, b, c;
};
struct Derived : Base {
  int b;
};
struct Derived2 : Derived {
  int c;
};

Здесь объект класса Derived2 будет иметь подобъект класса, Derived который, в свою очередь, будет иметь подобъект класса Base. ]end example

А, base-specifierза которым следует многоточие, - это pack expansion.

Порядок, в котором подобъекты базового класса размещаются в most derived object не определен. [ Note: Производный класс и подобъекты его базового класса могут быть представлены направленным ациклическим графом (DAG), где стрелка означает «непосредственно производный от». Стрелке не обязательно иметь физическое представление в памяти. DAG подобъектов часто называют «решеткой подобъектов».

dag Base Base Base Derived1 Derived1 Derived1 Derived1->Base Derived1->Base Derived2 Derived2 Derived2 Derived2->Derived1 Derived2->Derived1
Рисунок 2 - Направленный ациклический граф

end note]

[ Note: Инициализация объектов, представляющих базовые классы, может быть указана в конструкторах; см [class.base.init]. ]end note

[ Note: Подобъект базового класса может иметь layout ([basic.stc]), отличный от макета наиболее производного объекта того же типа. Подобъект базового класса может иметь полиморфное поведение ([class.cdtor]), отличное от полиморфного поведения наиболее производного объекта того же типа. Подобъект базового класса может иметь нулевой размер (пункт [class]); однако два подобъекта, которые имеют один и тот же тип класса и принадлежат одному и тому же наиболее производному объекту, не должны размещаться по одному и тому же адресу ([expr.eq]). ]end note

13.1 Multiple base classes [class.mi]

Класс может быть производным от любого количества базовых классов. [ Note: Использование более одного прямого базового класса часто называется множественным наследованием. ] [end noteExample:

class A { /* ... */ };
class B { /* ... */ };
class C { /* ... */ };
class D : public A, public B, public C { /* ... */ };

end example]

[ Note: Порядок вывода не имеет значения, за исключением случаев, указанных в семантике инициализации с помощью конструктора ([class.base.init]) cleanup, и разметки хранилища ([class.mem], [class.access.spec]). ]end note

Класс не должен указываться как прямой базовый класс производного класса более одного раза. [ Note: Класс может быть косвенным базовым классом более одного раза и может быть прямым и косвенным базовым классом. Есть ограниченное количество вещей, которые можно сделать с таким классом. На нестатические элементы данных и функции-члены прямого базового класса нельзя ссылаться в области производного класса. Однако на статические члены, перечисления и типы можно однозначно ссылаться. ] [end noteExample:

class X { /* ... */ };
class Y : public X, public X { /* ... */ };             // ill-formed
class L { public: int next;  /* ... */ };
class A : public L { /* ... */ };
class B : public L { /* ... */ };
class C : public A, public B { void f(); /* ... */ };   // well-formed
class D : public A, public L { void f(); /* ... */ };   // well-formed

end example]

Спецификатор базового класса, не содержащий ключевого слова, virtual задает non-virtual base class. Спецификатор базового класса, содержащий ключевое слово, virtual задает virtual base class. Для каждого отдельного вхождения невиртуального базового класса в решетку классов самого производного класса most derived object должен содержать соответствующий отдельный подобъект базового класса этого типа. Для каждого отдельного базового класса, который указан как виртуальный, наиболее производный объект должен содержать единственный подобъект базового класса этого типа.

[ Note: Для объекта типа класса Cкаждое отдельное вхождение (не виртуального) базового класса L в решетку классов C однозначно соответствует отдельному L подобъекту внутри объекта типа C. Учитывая класс, C определенный выше, объект класса C будет иметь два подобъекта класса, L как показано на рисунке [fig:nonvirt].

nonvirt L1 L1 L L2 L2 L A A A A->L1 A->L1 B B B B->L2 B->L2 C C C C->A C->A C->B C->B
Рисунок 3 - Невиртуальная база

В таких решетках можно использовать явную квалификацию, чтобы указать, какой подобъект имеется в виду. Тело функции C​::​f может относиться к члену next каждого L подобъекта:

void C::f() { A::next = B::next; }      // well-formed

Без квалификаторов A​::​ или B​::​ приведенноеC​::​f выше определение было бы некорректным из-за ambiguity. ]end note

[ Note: Напротив, рассмотрим случай с виртуальным базовым классом:

class V { /* ... */ };
class A : virtual public V { /* ... */ };
class B : virtual public V { /* ... */ };
class C : public A, public B { /* ... */ };
virt V V V A A A A->V A->V B B B B->V B->V C C C C->A C->A C->B C->B
Рисунок 4 - Виртуальная база

Для объекта c типа класса Cединственный подобъект типа V является общим для каждого подобъекта базового класса c , имеющего virtual базовый класс типа V. Учитывая класс, C определенный выше, объект класса C будет иметь один подобъект класса V, как показано на рисунке [fig:virt]. ]end note

[ Note: Класс может иметь как виртуальные, так и невиртуальные базовые классы данного типа.

class B { /* ... */ };
class X : virtual public B { /* ... */ };
class Y : virtual public B { /* ... */ };
class Z : public B { /* ... */ };
class AA : public X, public Y, public Z { /* ... */ };

Для объекта класса AAвсе virtual вхождения базового класса B в решетке классов AA соответствуют одному B подобъекту в объекте типа AA, а каждое другое вхождение (не виртуального) базового класса B в решетке классов AA соответствует взаимно-однозначно. один с отдельным B подобъектом внутри объекта типа AA. Учитывая класс, AA определенный выше, у класса AA есть два подобъекта класса B: Zs B и виртуальный, B совместно используемый X и Y, как показано на рисунке [fig:virtnonvirt].

virtnonvirt B1 B1 B B2 B2 B AA AA AA X X X AA->X AA->X Y Y Y AA->Y AA->Y Z Z Z AA->Z AA->Z X->B1 X->B1 Y->B1 Y->B1 Z->B2 Z->B2
Рисунок 5 - Виртуальная и не виртуальная база

end note]

13.2 Member name lookup [class.member.lookup]

Поиск имени члена определяет значение имени ( id-expression) в файле class scope. Поиск имени может привести к ошибке ambiguity, и в этом случае программа имеет неправильный формат . Для того id-expression, поиск имя начинается в классе сфере this; для qualified-idпоиска по имени начинается в области nested-name-specifier. Name lookup имеет место раньше access control.

Следующие шаги определяют результат поиска имени для имени члена f в области класса C.

lookup set Для f ин C, называется S(f,C), состоит из двух наборов компонентов: в declaration set, набор членов с именем f; и subobject setнабор подобъектов, в которых using-declarationsбыли найдены объявления этих членов (возможно, включая ). В наборе объявлений using-declarations они заменяются набором назначенных членов, которые не скрыты и не переопределяются членами производного класса ([namespace.udecl]), а объявления типов (включая имена внедренных классов) заменяются типами, которые они обозначают. S(f,C) рассчитывается следующим образом:

Если C содержит объявление имени f, набор объявлений содержит каждое объявление, f объявленное в, C которое удовлетворяет требованиям языковой конструкции, в которой выполняется поиск. [ Note: Поиск имени в elaborated-type-specifier([basic.lookup.elab]) или base-specifier, например, игнорирует все объявления, не относящиеся к типу, а поиск имени в nested-name-specifier([basic.lookup.qual]) игнорирует объявления функций, переменных и перечислителей. В качестве другого примера, поиск имени в a using-declarationвключает в себя объявление класса или перечисления, которое обычно было бы скрыто другим объявлением этого имени в той же области. ] Если результирующий набор объявлений не пустой, набор подобъектов содержит сам себя, и расчет завершен.end note C

В противном случае (т.е. C не содержит объявления f или результирующий набор объявлений пуст) S(f,C) изначально пуст. Если C есть базовые классы, вычислите поисковый набор для f каждого прямого подобъекта базового класса Biи объедините каждый такой поисковый набор S(f,Bi) по очереди в S(f,C).

Следующие шаги определяют результат слияния набора поиска S(f,Bi) с промежуточным S(f,C):

  • Если каждый из членов подобъекта S(f,Bi) является подобъектом базового класса по крайней мере одного из членов подобъекта S(f,C)или S(f,Bi) пуст, S(f,C) не изменяется и слияние завершается. И наоборот, если каждый из членов подобъекта S(f,C) является подобъектом базового класса по крайней мере одного из членов подобъекта S(f,Bi), или, если S(f,C) он пуст, новый S(f,C) является копией S(f,Bi).

  • В противном случае, если наборы объявлений S(f,Bi) и S(f,C) различаются, слияние будет неоднозначным: новый S(f,C) набор поиска с недопустимым набором объявлений и объединением наборов подобъектов. При последующих слияниях недействительный набор объявлений считается отличным от любого другого.

  • В противном случае новый S(f,C) - это поисковый набор с общим набором объявлений и объединением наборов подобъектов.

Результатом поиска имени f в C является набор объявлений S(f,C). Если это недопустимый набор, программа имеет неправильный формат. [Example:

struct A { int x; };                    // S(x,A) = { { A​::​x }, { A } }
struct B { float x; };                  // S(x,B) = { { B​::​x }, { B } }
struct C: public A, public B { };       // S(x,C) = { invalid, { A in C, B in C } }
struct D: public virtual C { };         // S(x,D) = S(x,C)
struct E: public virtual C { char x; }; // S(x,E) = { { E​::​x }, { E } }
struct F: public D, public E { };       // S(x,F) = S(x,E)
int main() {
  F f;
  f.x = 0;                              // OK, lookup finds E​::​x
}

S(x,F) однозначно , так как A и B базовые субобъекты класса из D также базовые субобъекты класса из E, так чтоS(x,D) отбрасываются на первом этапе слияния. ]end example

Если имя перегруженной функции обнаружено однозначно, overload resolution также выполняется до управления доступом. Неоднозначности часто можно разрешить, уточнив имя с именем класса. [Example:

struct A {
  int f();
};
struct B {
  int f();
};
struct C : A, B {
  int f() { return A::f() + B::f(); }
};

end example]

[ Note: Статический член, вложенный тип или перечислитель, определенные в базовом классе, T могут быть однозначно найдены, даже если объект имеет более одного подобъекта базового класса типа T. Два подобъекта базового класса совместно используют нестатические подобъекты-члены своих общих виртуальных базовых классов. ] [end noteExample:

struct V {
  int v;
};
struct A {
  int a;
  static int   s;
  enum { e };
};
struct B : A, virtual V { };
struct C : A, virtual V { };
struct D : B, C { };

void f(D* pd) {
  pd->v++;          // OK: only one v (virtual)
  pd->s++;          // OK: only one s (static)
  int i = pd->e;    // OK: only one e (enumerator)
  pd->a++;          // error, ambiguous: two as in D
}

end example]

[ Когда используются виртуальные базовые классы, скрытое объявление может быть достигнуто по пути через решетку подобъектов, который не проходит через скрытое объявление. Это не двусмысленность. Идентичное использование с невиртуальными базовыми классами является неоднозначным; в этом случае не существует уникального экземпляра имени, скрывающего все остальные. ] [Note: end noteExample:

struct V { int f();  int x; };
struct W { int g();  int y; };
struct B : virtual V, W {
  int f();  int x;
  int g();  int y;
};
struct C : virtual V, W { };

struct D : B, C { void glorp(); };
virt W1 W1 W V V V W2 W2 W B B B B->W1 B->W1 B->V B->V C C C C->V C->V C->W2 C->W2 D D D D->B D->B D->C D->C
Рисунок 6 - Поиск имени

Имена, объявленные в V и левый экземпляр W скрыты внутри B, но имена, объявленные в правом экземпляре W , не скрываются вообще.

void D::glorp() {
  x++;              // OK: B​::​x hides V​::​x
  f();              // OK: B​::​f() hides V​::​f()
  y++;              // error: B​::​y and C's W​::​y
  g();              // error: B​::​g() and C's W​::​g()
}

end example]

Явное или неявное преобразование указателя или выражения, обозначающего объект производного класса, в указатель или ссылку на один из его базовых классов должно однозначно относиться к уникальному объекту, представляющему базовый класс. [Example:

struct V { };
struct A { };
struct B : A, virtual V { };
struct C : A, virtual V { };
struct D : B, C { };

void g() {
  D d;
  B* pb = &d;
  A* pa = &d;       // error, ambiguous: C's A or B's A?
  V* pv = &d;       // OK: only one V subobject
}

end example]

[ Note: Даже если результат поиска имени однозначен, использование имени , найденное в нескольких подобъектах все еще может быть неоднозначным ([conv.mem], [expr.ref], [class.access.base]). ] [end noteExample:

struct B1 {
  void f();
  static void f(int);
  int i;
};
struct B2 {
  void f(double);
};
struct I1: B1 { };
struct I2: B1 { };

struct D: I1, I2, B2 {
  using B1::f;
  using B2::f;
  void g() {
    f();                        // Ambiguous conversion of this
    f(0);                       // Unambiguous (static)
    f(0.0);                     // Unambiguous (only one B2)
    int B1::* mpB1 = &D::i;     // Unambiguous
    int D::* mpD = &D::i;       // Ambiguous conversion
  }
};

end example]

13.3 Virtual functions [class.virtual]

[ 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-specifier final и в классе, D производном от переопределения B функции , программа имеет неправильный формат. [ D​::​f B​::​fExample:

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-specifier override и не переопределяет функцию-член базового класса, программа имеет неправильный формат. [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 не учитывается при определении приоритета.

Многоуровневые указатели на классы или ссылки на многоуровневые указатели на классы не допускаются.

13.4 Abstract classes [class.abstract]

[ Note: Механизм абстрактного класса поддерживает понятие общего понятия, такого как a shape, из которого фактически могут использоваться только более конкретные варианты, такие как circle и square. Абстрактный класс также можно использовать для определения интерфейса, для которого производные классы предоставляют множество реализаций. ]end note

An abstract class - это класс, который может использоваться только как базовый класс какого-либо другого класса; никакие объекты абстрактного класса не могут быть созданы, кроме как подобъекты класса, производного от него. Класс является абстрактным, если в нем есть хотя бы один pure virtual function. [ Note: Такая функция может быть унаследована: см. Ниже. ] Виртуальная функция указывается с помощью символа a в объявлении функции в определении класса. Чисто виртуальная функция должна быть определена только тогда , когда вызываются с, или как будто с ( ), то синтаксис ( ). [end note pure pure-specifier[class.dtor]qualified-id[expr.prim]Example:

class point { /* ... */ };
class shape {                   // abstract class
  point center;
public:
  point where() { return center; }
  void move(point p) { center=p; draw(); }
  virtual void rotate(int) = 0; // pure virtual
  virtual void draw() = 0;      // pure virtual
};

end example] [ Note: Объявление функции не может содержать одновременно pure-specifier и определение ] [end noteExample:

struct C {
  virtual void f() = 0 { };     // ill-formed
};

end example]

Абстрактный класс не должен использоваться как тип параметра, как тип возвращаемого значения функции или как тип явного преобразования. Можно объявить указатели и ссылки на абстрактный класс. [Example:

shape x;                        // error: object of abstract class
shape* p;                       // OK
shape f();                      // error
void g(shape);                  // error
shape& h(shape&);               // OK

end example]

Класс является абстрактным, если он содержит или наследует хотя бы одну чистую виртуальную функцию, для которой последний переопределитель является чисто виртуальной. [Example:

class ab_circle : public shape {
  int radius;
public:
  void rotate(int) { }
  // ab_­circle​::​draw() is a pure virtual
};

Поскольку shape​::​draw() это чистая виртуальная функция,ab_­circle​::​draw() по умолчанию это чистая виртуальная функция . Альтернативная декларация,

class circle : public shape {
  int radius;
public:
  void rotate(int) { }
  void draw();                  // a definition is required somewhere
};

сделает класс circle неабстрактным, и circle​::​draw() должно быть дано определение. ]end example

[ Note: Абстрактный класс может быть производным от класса, который не является абстрактным, а чистая виртуальная функция может переопределить виртуальную функцию, которая не является чистой. ]end note

Функции-члены могут быть вызваны из конструктора (или деструктора) абстрактного класса; эффект преобразования a virtual call в чистую виртуальную функцию прямо или косвенно для объекта, создаваемого (или уничтожаемого) из такого конструктора (или деструктора), не определен.