Неявный вызов конструктора недоступного виртуального базового класса

Рассмотрим код ниже. И g++, и clang++ жалуются (правильно), что конструктор A(int) является закрытым в классе D. Обратите внимание, что, поскольку A является виртуальным базовым классом D, A должен быть инициализирован в mem-initializer класса D, наиболее производного класса, в соответствии с §12.6.2/7 в C+. +11. См. живой пример.

class A {
public:
    A(int i) : x(i) { }
    A() : x(1) {}
    int x;
};

class B : private virtual A {
protected:
    B(int i) : A(i) { } };

class C : public B, private virtual A {
protected:
    C(int i) : A(i), B(i) { }
};

class D : public C {
public:
    D() : A(1), C(3) { }
};

int main() {
    D d;
}

Но оба компилятора не беспокоятся о том, что конструктор по умолчанию для класса A также является приватным в D, т.е. и компилирует, и выполняет код нормально, если мы определим конструктор для D следующим образом:

D() : C(3) {}

И это неправильно, насколько я могу судить.

Обратите внимание, что оба компилятора не смогут скомпилировать (правильно), если мы определим:

D() : A(), C(3) {}

person Belloc    schedule 27.02.2015    source источник
comment
Возможно, вы захотите добавить живой пример, который показывает случай, который вас озадачил, т. е. D() : C(3) {}   -  person Shafik Yaghmour    schedule 27.02.2015
comment
@RSahu Даже для случая D(): C(3) {}, указанного выше? Я спрашиваю об этом, потому что я действительно не знаю разницы между компилятором, который я использовал в Coliru (стандартный std=C++11), и 4.7.3, о котором вы упоминали выше.   -  person Belloc    schedule 27.02.2015
comment
@RSahu С D() : C(3) я не получаю ошибок с GCC 4.7.4. Какие параметры командной строки вы используете с 4.7.3, чтобы получить ошибку?   -  person    schedule 27.02.2015
comment
Я получил ту же ошибку error: 'class A A::A' is inaccessible на ideone: ideone.com/n9ywWo   -  person drescherjm    schedule 27.02.2015
comment
@ShafikYaghmour Вот: coliru.stacked-crooked.com/a/df0b851b4f6ab1ac   -  person Belloc    schedule 27.02.2015
comment
@hvd, у меня такое же поведение, что подтверждает опасения ОП.   -  person R Sahu    schedule 27.02.2015
comment
Забавно, но в MSVC код компилируется даже при использовании D() : A(1), C(3) {}... для меня это абсолютно бессмысленно.   -  person wolfPack88    schedule 27.02.2015
comment
@RSahu Но правильно, согласно ОП, это означало бы получение ошибки. Если у вас такое же поведение, как у меня, и вы говорите, что поведение правильное, то я что-то упускаю.   -  person    schedule 27.02.2015
comment
@ wolfPack88 Но это похоже на ошибку в Visual Studio. Я тоже это пробовал.   -  person Belloc    schedule 27.02.2015
comment
@hvd, нет, ты ничего не пропустил. Я был немного смущен. Я удалил свои комментарии.   -  person R Sahu    schedule 27.02.2015
comment
FWIW, Intel, Sun и даже TenDRA согласны с тем, что это не ошибка. Это делает наиболее вероятным, что на самом деле есть какое-то объяснение, чтобы сделать код действительным. Но у меня нет такого объяснения.   -  person    schedule 27.02.2015


Ответы (1)


Но оба компилятора не беспокоятся о том, что конструктор по умолчанию для класса A также является закрытым в D,

Нет, этот конструктор по умолчанию не является закрытым. Базовый класс A является закрытым, но его конструктор по умолчанию является общедоступным.

И вот почему это работает: при именовании базовых классов в ctor-initializer именованные базовые классы должны быть доступны, потому что к именам применяется контроль доступа, и есть несколько особых исключений, когда стандарт говорит, что это неявно вызываемые функции по-прежнему должны быть доступны.

Когда базовые классы создаются неявно, эти базовые классы не называются. Они просто инициализируются по умолчанию (согласно 12.6.2p8), а инициализация по умолчанию просто проверяет, доступен ли конструктор (согласно 8.5p7).

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

D() : ::A(1), C(3) { }

Живой пример

person Community    schedule 27.02.2015
comment
Конструктор по умолчанию является общедоступным, только если он не предоставляется пользователем. - person Belloc; 27.02.2015
comment
@Belloc В этом нет смысла. Пожалуйста, перечитайте свой комментарий, подумайте, что вы хотите сказать, а затем перефразируйте его. - person ; 27.02.2015
comment
@Belloc Я говорю о конструкторе по умолчанию, который вы определили. A() : x(1) {}. Вы помещаете это под public:, так что конструктор по умолчанию является общедоступным. Возможно, вы имеете в виду какой-то другой язык, в котором конструктор по умолчанию относится только к конструктору, сгенерированному компилятором? В С++ такого нет. 12.1p5, перед вашей цитатой: конструктор по умолчанию для класса X — это конструктор класса X, который можно вызывать без аргумента. - person ; 27.02.2015
comment
Тогда ответьте мне, как конструктор A(int) наследуется частным образом в D, а конструктор по умолчанию, предоставляемый пользователем, - нет? - person Belloc; 27.02.2015
comment
@Belloc Ни один из конструкторов не наследуется. - person ; 27.02.2015
comment
Хорошо, почему конструктор A(int) недоступен в D, а конструктор, предоставленный пользователем, недоступен? Они оба общедоступны в A. - person Belloc; 27.02.2015
comment
@Belloc Они оба доступны. Однако базовый класс A не является таковым. Это то, на что я указываю в своем ответе: D() : A() или D() : A(3) не терпит неудачу, потому что конструктор недоступен, он терпит неудачу, потому что базовый класс недоступен. - person ; 27.02.2015
comment
Я начинаю понимать, что вы сказали в своем ответе. Но где это указано в стандарте? - person Belloc; 27.02.2015
comment
@ Belloc 12.6.2p8 просто говорит, что подобъекты инициализируются по умолчанию, как я уже упоминал, без какого-либо запрета на доступ, кроме косвенного запрета на доступность конструктора по умолчанию. Именно отсутствие запрета делает его действительным. Когда вы делаете имя базового класса в списке инициализатора, применяется общее правило 11p4 (управление доступом применяется одинаково ко всем именам, независимо от того, ссылаются ли имена из объявлений или выражений). - person ; 27.02.2015
comment
Вы можете быть правы, но мне очень трудно принять вашу интерпретацию, просто прочитав Стандарт. Например, когда вы говорите: They are both accessible. However, the base class A is not., это не имеет для меня особого смысла. И если это так, я думаю, что Стандарт должен быть более четким по этому поводу. - person Belloc; 27.02.2015
comment
@Belloc Стандарт не предназначен для использования в качестве учебного пособия, он должен быть максимально однозначным, даже если это затрудняет его понимание. Есть действительно много аспектов, которые вы не могли бы легко понять, просто прочитав стандарт. С базовым классом дело в том, что у каждого класса есть имя внедренного класса, которое в основном рассматривается как обычный член, в том числе во время наследования членов базового класса. struct A { }; имеет член A, который фактически является определением типа struct A. Затем struct B : private A { }; наследует имя введенного класса A и делает его private. - person ; 27.02.2015
comment
@Belloc Итак, когда у вас есть struct C : B { C() { delete new A(); } }; (что просто глупо и служит просто примером), это недействительно, даже если это не попытка доступа к частному базовому классу B. - person ; 27.02.2015
comment
@Belloc Инициализаторы базового класса в списке инициализации конструктора указываются путем ссылки на типы, а не на конструкторы. Эти типы могут быть указаны любым способом, которым задаются типы, включая определения типов, в том числе введенные имена классов, но имена, используемые при указании типа, должны быть доступны так же, как и любые другие имена. Имеет ли это смысл? - person ; 27.02.2015
comment
12.6.2p8 just says subobjects are default-initialized, like I mentioned already, without any prohibition on accessibility aside from indirectly that of the default constructor. It's the lack of prohibition that makes it valid. Я бы сказал, что это не совсем правильно. §12.6.2/8 относится к §8.5, а в §8.5/6 первый пункт списка: if T is a (possibly cv-qualified) class type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);. - person Belloc; 27.02.2015
comment
@Belloc Я уже упоминал об этом в своем ответе: а инициализация по умолчанию просто проверяет, доступен ли конструктор (согласно 8.5p7). Но конструктор доступен, так что это не проблема. - person ; 27.02.2015
comment
Я наконец понял, что здесь происходит. Спасибо за отличный ответ (+1). - person Belloc; 07.03.2015
comment
@curiousguy Если у вас есть лучший ответ, пожалуйста, опубликуйте его, но я подробно объяснил, почему я считаю, что это правильно, и комментарий, который немного больше, чем нет, не убедит меня в обратном. - person ; 12.07.2018
comment
Проще говоря, любой элемент, найденный с помощью закрытого наследования, является закрытым и недоступным. std не читается при вызовах ctor, но цель ясна. - person curiousguy; 12.07.2018
comment
@curiousguy Конструктор не найден через наследование. В этом весь смысл моего ответа. Кстати, gcc, clang и Intel согласны с этим. - person ; 12.07.2018
comment
Интересный. Какие объявления найдены через наследование? Можете ли вы указать на исчерпывающий список? - person curiousguy; 12.07.2018
comment
@curiousguy Я не понимаю, почему вы заключаете найденное через наследование в кавычки. Создается впечатление, что это термин, который я придумал, но я только что использовал ваши слова. Поскольку конструкторы изначально не наследуются, их невозможно найти с помощью наследования. Все, кроме конструкторов, не имеет отношения к этому вопросу и ответу, поэтому я не буду искать список. - person ; 12.07.2018
comment
Значит, контроль доступа не применяется единообразно ко всем объявлениям? - person curiousguy; 13.07.2018