Неявно извикване на конструктор на недостъпен виртуален базов клас

Разгледайте кода по-долу. И 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, получавам същото поведение, което потвърждава загрижеността на OP.   -  person R Sahu    schedule 27.02.2015
comment
Забавно е, че на MSVC кодът се компилира дори когато се използва D() : A(1), C(3) {}... това няма абсолютно никакъв смисъл за мен.   -  person wolfPack88    schedule 27.02.2015
comment
@RSahu Но правилно, според OP, би означавало получаване на грешка. Ако получавате същото поведение като мен и казвате, че това поведение е правилно, тогава пропускам нещо.   -  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:, така че конструкторът по подразбиране е публичен. Може би си мислите за някакъв друг език, където конструкторът по подразбиране се отнася само до генериран от компилатор конструктор? Това не е така в C++. 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, който на практика е typedef на 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 И инициализаторите на базов клас в списък за инициализация на конструктор са посочени чрез позоваване на типове, а не чрез позоваване на конструктори. Тези типове могат да бъдат специфицирани по всякакъв начин, по който типовете се специфицират, включително typedefs, включително имена на инжектирани класове, но имената, използвани при определяне на типа, трябва да бъдат достъпни точно както всички други имена. Има ли логика в това? - 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