Подтвердить что ты не робот

Задача виртуального наследования С++

В следующем коде кажется, что класс C не имеет доступа к конструктору, который требуется из-за виртуального наследования. Тем не менее, код все еще компилируется и запускается. Почему это работает?

class A {};
class B: private virtual A {};
class C: public B {};

int main() {
    C c;
    return 0;
}

Кроме того, если я удаляю конструктор по умолчанию из A, например

class A {
public:
    A(int) {}
};
class B: private virtual A {
public:
    B() : A(3) {}
};

затем

class C: public B {};

будет (неожиданно) компилироваться, но

class C: public B {
public:
    C() {}
};

не будет компилироваться, как и ожидалось.

Код, скомпилированный с помощью "g++ (GCC) 3.4.4 (cygming special, gdc 0.12, с использованием dmd 0.125)", но было проверено, что оно ведет себя аналогично с другими компиляторами.

4b9b3361

Ответ 1

В соответствии с С++ Core Issue # 7 класс с виртуальной частной базой не может быть получен. Это ошибка в компиляторе.

Ответ 2

Для второго вопроса это, вероятно, потому, что вы не определяете его неявным образом. Если конструктор просто объявлен неявно, ошибки нет. Пример:

struct A { A(int); };
struct B : A { };
// goes fine up to here

// not anymore: default constructor now is implicitly defined 
// (because it used)
B b;

Для вашего первого вопроса - это зависит от того, какое имя использует компилятор. Я понятия не имею, что специфицирует стандарт, но этот код является верным, потому что доступно имя внешнего класса (вместо имени наследуемого класса):

class A {};
class B: private virtual A {};
class C: public B { C(): ::A() { } }; // don't use B::A

Возможно, на данный момент стандарт не указан. Мы должны будем посмотреть.


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

12.6.2/8 (С++ 0x)

Если заданный нестатический член данных или базовый класс не назван идентификатором mem-initializer (включая случай где нет mem-initializer-list, потому что у конструктора нет ctor-initializer), и сущность не является виртуальным базовым классом абстрактного класса

[...], в противном случае объект инициализируется по умолчанию

И С++ 03 имеет похожий текст (менее понятный текст - он просто говорит, что его конструктор по умолчанию вызывается в одном месте, а с другой - зависит от того, является ли класс POD). Для компилятора по умолчанию инициализировать подобъект, он просто должен вызвать его конструктор по умолчанию - нет необходимости сначала искать имя базового класса (он уже знает, какая база считается).

Рассмотрим этот код, который, безусловно, должен быть действительным, но это не сработает, если это будет сделано (см. 12.6.2/4 в С++ 0x)

struct A { };
struct B : virtual A { };
struct C : B, A { };
C c;

Если конструктор по умолчанию для компилятора будет просто искать имя класса A внутри C, он будет иметь неоднозначный результат поиска в отношении того, какой подобъект будет инициализирован, поскольку как не виртуальный A, так и виртуальные A имена классов. Если ваш код будет плохо организован, я бы сказал, что стандарт, безусловно, нуждается в уточнении.


Для конструктора обратите внимание на то, что 12.4/6 говорит о деструкторе C:

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

Это можно интерпретировать двумя способами:

  • вызов A:: ~ A()
  • вызов:: A:: ~ A()

Мне кажется, что стандарт здесь менее ясен. Второй способ сделает его действительным (через 3.4.3/6, С++ 0x, потому что оба класса-имен A просматриваются в глобальной области), а первый сделает его недействительным (поскольку оба A найдут унаследованные имена классов). Это также зависит от того, какой подобъект начинается с поиска (и я считаю, что нам придется использовать подобъект виртуального базового класса в качестве начальной точки). Если это похоже на

virtual_base -> A::~A();

Затем мы будем непосредственно находить имя класса виртуальной базы как общедоступное имя, потому что нам не придется проходить через области производного класса и найти это имя как недоступное. Опять же, рассуждения похожи. Рассмотрим:

struct A { };
struct B : A { };
struct C : B, A {
} c;

Если деструктор просто вызовет this->A::~A(), этот вызов не будет действителен из-за неоднозначного результата поиска A в качестве унаследованного имени класса (вы не можете ссылаться на какую-либо нестационарную функцию-член прямой базы объект класса из области C, см. 10.1/3, С++ 03). Он однозначно должен идентифицировать имена классов, которые задействованы, и должен начинать с ссылки на подобъект класса, например a_subobject->::A::~A();.

Ответ 3

Виртуальные базовые классы всегда инициализируются из самых производных классов (здесь C). Компилятор должен проверить, что конструктор доступен (т.е. Я получаю сообщение об ошибке с g++ 3.4 для

class A { public: A(int) {} };
class B: private virtual A {public: B() : A(0) {} };
class C: public B {};

int main() {
    C c;
    return 0;
}

в то время как ваше описание подразумевает, что нет ни одного), но тот факт, что в качестве базы А является закрытым или нет, не имеет значения (подойти было бы легко: class C: public B, private virtual A).

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

Изменить: Кирилл упомянул о старой проблеме, которая нечетна с моим чтением и поведением последних компиляторов. Я попытаюсь получить стандартные ссылки так или иначе, но это может занять некоторое время.