Проблема частного виртуального наследования C ++ - PullRequest
20 голосов
/ 03 марта 2010

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

Ответы [ 3 ]

14 голосов
/ 03 марта 2010

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

6 голосов
/ 03 марта 2010

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

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-initializer-id (включая регистр где нет 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();.

2 голосов
/ 03 марта 2010

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

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

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

...