C++ 3d.Комментарии

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


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

Следует отметить, что эти "некоторые ослабления" не являются простой формальностью. Рассмотрим следующий пример: #include <stdio.h>

struct B1 { int b1; // непустая virtual ~B1() { } };

struct B2 { int b2; // непустая

virtual B2* vfun() { printf("B2::vfun()\n"); // этого мы не должны увидеть return this; } };

struct D : B1, B2 { // множественное наследование от непустых классов virtual D* vfun() { printf("D::vfun(): this=%p\n", this); return this; } };

int main() { D d;

D* dptr=&d; printf("dptr\t%p\n", dptr);

void* ptr1=dptr->vfun(); printf("ptr1\t%p\n", ptr1);

B2* b2ptr=&d; printf("b2ptr\t%p\n", b2ptr);

void* ptr2=b2ptr->vfun(); printf("ptr2\t%p\n", ptr2); }

Обратите внимание: в данном примере я воспользовался "некоторыми ослаблениями" для типа возвращаемого значения D::vfun(), и вот к чему это привело: dptr 0012FF6C D::vfun(): this=0012FF6C ptr1 0012FF6C

b2ptr 0012FF70 D::vfun(): this=0012FF6C ptr2 0012FF70

Т.о. оба раза была вызвана D::vfun(), но возвращаемое ей значение зависит от способа вызова (ptr1!=ptr2), как это, собственно говоря, и должно быть.

Делается это точно так же, как уже было описано в разделе 361 "12.2.6. Виртуальные функции", только помимо корректировки принимаемого значения this необходимо дополнительно произвести корректировку this возвращаемого. Понятно, что виртуальные функции с ковариантным типом возврата встречаются настолько редко, что реализация их вызова посредством расширения vtbl вряд ли может быть признана адекватной. На практике обычно создаются специальные функции-заглушки, чьи адреса помещаются в соответствующие элементы vtbl: // псевдокод



// оригинальная D::vfun, написанная программистом D* D::vfun(D *const this) { // ... }

// сгенерированная компилятором функция-заглушка для вызова D::vfun() через // указатель на базовый класс B2 B2* D::vfun_stub(B2 *const this) { return D::vfun(this+delta_1)+delta_2; }


где возвращаемый функцией указатель корректируется посредством константы delta_2, вообще говоря, не равной delta_1.

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

10.3. Виртуальные функции [class.virtual]

Тип возвращаемого значения замещающей функции может быть или идентичен типу замещаемой функции или быть ковариантным (covariant). Если функция D::f замещает функцию B::f, типы возвращаемых ими значений будут ковариантными, если они удовлетворяют следующим условиям:


    они оба являются указателями или ссылками на класс (многоуровневые указатели или ссылки на многоуровневые указатели запрещены)

    класс в возвращаемом значении B::f идентичен классу в возвращаемом значении D::f или он является однозначно определенным открытым прямым или косвенным базовым классом возвращаемого D::f класса и при этом доступен в D

    как указатели так и ссылки имеют идентичные cv-квалификаторы и, при этом, класс возвращаемого значения D::f имеет те же или меньшие cv-квалификаторы, что и класс в возвращаемом значении B::f.

    Если тип возвращаемого значения D::f отличается от типа возвращаемого значения B::f, то тип класса в возвращаемом значении D::f должен быть завершен в точке определения D::f или он должен быть типом D. Когда замещающая функция будет вызывана (как последняя заместившая функция), тип ее возвращаемого значения будет (статически) преобразован в тип возвращаемого значения замещаемой функции (5.2.2). Например: 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(); // ошибка: B (базовый класс D) недоступен };

    class A; struct Derived : public Base { void vf1(); // виртуальная и замещает Base::vf1() void vf2(int); // не виртуальная, скрывает Base::vf2() char vf3(); // ошибка: неправильный тип возвращаемого значения D* vf4(); // OK: возвращает указатель на производный класс A* vf5(); // ошибка: возвращает указатель на незавершенный класс void f(); };



    void g() { Derived d; Base* bp=&d; // стандартное преобразование: Derived* в Base* bp->vf1(); // вызов Derived::vf1() bp->vf2(); // вызов Base::vf2() bp->f(); // вызов Base::f() (не виртуальная) B* p=bp->vf4(); // вызов Derived::pf() и преобразование // возврата в B* Derived* dp=&d; D* q=dp->vf4(); // вызов Derived::pf(), преобразование // результата в B* не осуществляется dp->vf2(); // ошибка: отсутствует аргумент }

    А что означает загадочная фраза "меньшие cv-квалификаторы"?

    3.9.3. CV-квалификаторы [basic.type.qualifier]

    Множество cv-квалификаторов является частично упорядоченным:

    нет cv-квалификатора < const
    нет cv-квалификатора < volatile
    нет cv-квалификатора < const volatile
    const < const volatile
    volatile < const volatile

    Содержание раздела