Передача указателя недоступного частного базового типа методу производного класса - программирование
Подтвердить что ты не робот

Передача указателя недоступного частного базового типа методу производного класса

В этом примере кода описывается функция языка, которую я считаю неинтуитивной.

class A {
public:
  A() {}
};

class B: private A
{
public:
  B() {}
};

class C: public B
{
public:
  C() {}
  void ProcessA(A* a) {
  }
};

int main() {
  C c;
}

Компиляция этого кода с Apple LLVM версии 4.2 на Mac создает

test.cc:16: error: ‘class A’ is inaccessible
test.cc:16: error: within this context

Замена void ProcessA(A* a) на void ProcessA(::A* a) приведет к его созданию, но я не понимаю, почему я должен использовать абсолютное имя класса здесь. Является ли это функцией языка, которая позволяет избежать определенных ошибок или это просто темный угол грамматики С++, как требование разместить пространство между угловыми скобками (> >) в шаблонах, параметризованных другими шаблонами. Спасибо!

4b9b3361

Ответ 1

Человеческий разговор

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

Компилятор разрешает неквалифицированный A до ::C::A (результат будет таким же, если вы сами сделаете изменение на уровне источника). Поскольку ::C::A недоступен, выводится сообщение об ошибке.

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

Автоматическое угадывание того, что должно быть сделано здесь, не только подвержено появлению ошибок и/или вытягиванию волос¹, но и полностью противоречит духу С++.

Standardese

Подтверждение того, что это поведение является согласованным и по-дизайну, непосредственно из стандарта С++ 11.

В § 9/2 говорится:

Имя класса вставляется в область, в которой она объявлена сразу после просмотра имени класса. Название класса также вставлен в сферу действия самого класса; это известно как впрыскивается класса имя.

Это означает, что внутри области класса C A - это имя введенного класса.

В §3.4/3 указано, что имя введенного класса является кандидатом на поиск имен:

Введенное класс-класс класса также считается членом этого класса для целей скрытия имени и поиска.

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

Правила доступа рассматриваются только после поиска и функции имени разрешение перегрузки (если применимо) выполнено.

§11.1/5 дает прямое объяснение конкретной ситуации:

[Примечание: в производном классе поиск имени базового класса найдет имя введенного класса вместо имени базового класса в область, в которой она была объявлена. Имя введенного класса может быть меньше доступный, чем имя базового класса в области, в которой он был объявлен. -end note]

Стандарт также дает этот пример, который эквивалентен вашему:

class A { };
class B : private A { };
class C : public B {
    A *p;   // error: injected-class-name A is inaccessible
    ::A *q; // OK
};

¹ Представьте, что произойдет, если A изначально является базой public, а затем становится private во время рефакторинга. Также представьте, что ::A и ::C::A не связаны. Вы ожидаете, что вызов типа a->foo() (который использовался для работы) потерпит неудачу, потому что foo больше не доступен, но вместо этого тип A изменился за вашей спиной, и теперь вы получаете "есть no method foo". А?!? И это, конечно, далеко от худшего, что могло случиться.