C++ частная виртуальная проблема наследования

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

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 особенный, gdc 0.12, с помощью dmd 0.125)", но он был проверен для поведения того же с другими компиляторами также.

20
задан curiousguy 4 August 2012 в 16:45
поделиться

3 ответа

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

14
ответ дан 30 November 2019 в 01:05
поделиться

Виртуальные базовые классы всегда инициализируются из наиболее производных классов (здесь 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;
}

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

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

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

2
ответ дан 30 November 2019 в 01:05
поделиться

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

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

// not anymore: default constructor now is implicitly defined 
// (because it's 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 (C++0x)

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

[...] иначе, сущность инициализируется по умолчанию

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

Рассмотрим этот код, который, конечно, должен быть корректным, но который потерпел бы неудачу, если бы это было сделано (см. 12.6. 2/4 в C++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, C++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, C++03). Она должна однозначно идентифицировать имена классов, которые в ней участвуют, и должна начинаться со ссылки на подобъект класса, как a_subobject->::A::~A();.

6
ответ дан 30 November 2019 в 01:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: