Различное поведение модификатора доступа в наследовании зависит от & quot; this & quot; ключевое слово и шаблоны или их отсутствие

Я хочу понять 4 различных поведения модификаторов доступа в отношении наследования, когда речь идет о 4 комбинациях использования и / или исключения templateс и this ключевое слово. Весь следующий код сделан в g ++ 4.8:

Вот GrandChild класс, который privateЛи наследует от Parent, который privateЛи наследует от GrandParent, который имеет public enum n, Необъектный, клиентский код может получить доступ GrandParent::nпотому что последний является public enum, Но GrandParent::n недоступен изнутри GrandChild:

#include <iostream>
using namespace std;

struct GrandParent { enum {n = 0}; };

struct Parent : private GrandParent { enum {n = 1}; };

struct GrandChild : private Parent {
enum {n = 2};
void f() {cout << GrandParent::n << endl;}
// ^ error: 'struct GrandParent GrandParent::GrandParent'
// is inaccessible
};
int main() {
cout << GrandParent::n << endl;
// ^ non-object access would have outputted `0` had `GrandChild`'s
// definition compiled or been commented out.
}

1.) GrandParent::nнедоступность изнутри GrandChild вызванный GrandChildвладение GrandParent базовый подобъект, который скрывает необъектный доступ к GrandParent::numи чье 2-поколение privateНесс делает базовый подобъект n также недоступен? Я ожидал, что сообщение об ошибке будет об этом.

2.) Но, видимо, это не так. Почему ошибка жалуется на GrandParentконструктор?

3.) Готовимся this-> в GrandParent::n в f()определение добавит ошибку, которую я ожидал в # 1, но не удалит жалобу ctor. Зачем? Я предполагал, что в том числе this-> является избыточным, и что его пропуск заставит поиск попытаться найти n из GrandParent подобъект внутри GrandChildобласть действия до объекта, который не имеет немедленной области видимости n тем не мение.

4.) Почему этот вариант шаблона компилируется? По функциональности он похож на не шаблонный:

#include <iostream>
using namespace std;

template <unsigned int N>
struct bar : private bar<N - 1> {
enum {num = N};
void g() {
static_assert(N >= 2, "range error");
cout << bar<N - 2>::num << endl;
}
};

template <>
struct bar<0> { enum {num = 0}; };

int main() {
bar<2> b2;
b2.g(); // Output: 0
}

5.) Готовимся this-> в bar<N - 2>::num в g()Определение вызывает ошибку компилятора, которую я ожидал только в # 1. Но почему это не включает ошибку # 2? И почему его упущение не приводит к ошибке № 2?

5

Решение

Вся проблема здесь — поиск имени (я думаю, что это также имело место в одном из ваших предыдущих вопросов). Я попытаюсь проиллюстрировать свое понимание происходящего:

Каждый (именованный) класс получает нагнетаемого имя класса. Например:

struct GrandParent
{
// using GrandParent = ::GrandParent;
enum {n = 0};
};

Вы можете использовать это имя введенного класса для ссылки на сам класс. Это не так полезно для обычных классов (где неквалифицированный поиск может найти имя GrandParent в любом случае), но для производных классов и шаблонов классов:

namespace A
{
struct Foo
{
// using Foo = ::A::Foo;
};
};

struct Bar : A::Foo
{
void woof(Foo); // using the injected-class-name `::A::Foo::Foo`
};

template<class T, int N, bool b>
struct my_template
{
// using my_template = ::my_template<T, N, b>;
void meow(my_template); // using the injected-class-name
};

Это не наследование, как в «он является частью подобъекта базового класса», но способ, которым указывается неквалифицированный поиск: если имя не найдено в области действия текущего класса, будет выполняться поиск областей базового класса.

Теперь для первого (не шаблонного) примера в OP:

struct Parent : private GrandParent
{
// using Parent = ::Parent;

enum {n = 1}; // hides GrandParent::n
};

struct GrandChild : private Parent {
// using GrandChild = ::GrandChild;

enum {n = 2};
void f() {cout << GrandParent::n << endl;}
// ^ error: 'struct GrandParent GrandParent::GrandParent'
// is inaccessible
};

Здесь выражение GrandParent::n вызывает поиск безусловного имени имени GrandParent, Поскольку неквалифицированный поиск останавливается, когда имя найдено (и не учитывает окружающие области), он найдет имя введенного класса GrandParent::GrandParent, То есть поиск ищет область действия GrandChild (имя не найдено), то объем Parent (имя не найдено) и, наконец, объем GrandParent (где он находит имя введенного класса). Готово до а также независим от проверка доступа.

После имени GrandParent был найден, доступность проверена в конце концов. Поиск имени требуется перейти от Parent в GrandParent найти имя. Этот путь заблокирован для всех, кроме участников и друзей Parent, так как наследство является частным. (Вы можете видеть этот путь, но не можете его использовать; видимость и доступность — это ортогональные понятия.)


Вот стандартный [basic.lookup.unqual] / 8:

Для членов класса Xимя, используемое в теле функции-члена […], должно быть объявлено одним из следующих способов:

  • перед его использованием в блоке, в котором он используется, или во вложенном блоке, или
  • должен быть членом класса X или быть членом базового класса X, или же
  • если X это вложенный класс класса Y […]
  • […]
  • если X является членом пространства имен Nили […] перед использованием имени,
    в пространстве имен N или в одном из NВмещающие пространства имен.

Поиск имени в базовых классах довольно сложен, поскольку может потребоваться рассмотреть несколько базовых классов. Для одиночного наследования и поиска члена в области тела функции-члена он начинается с класса, членом которого является эта функция, а затем пересекает базовые классы вверх (база, база базы, база базы базы, ..). Смотрите [class.member.lookup]


Случай шаблона отличается, так как bar Имя шаблона класса:

template <unsigned int N>
struct bar : private bar<N - 1> {
enum {num = N};
void g() {
static_assert(N >= 2, "range error");
cout << bar<N - 2>::num << endl;
}
};

Вот, bar<N - 2> используется. Это зависимое имя, так как N это параметр шаблона. Поэтому поиск имени откладывается до момента создания g, Специализация bar<0> может быть найден, даже если он объявлен после функции.

Имя введенного класса bar может использоваться как имя шаблона (ссылается на шаблон класса) или как имя типа (ссылается на текущую реализацию) [temp.local] / 1:

Как и обычные (не шаблонные) классы, шаблоны классов имеют имя введенного класса (раздел 9). Введенный
имя класса может использоваться как Имя Шаблона или Имя-типа. Когда он используется с Шаблон-аргумент-список,
как Шаблон-аргумент для шаблона Шаблон-параметр, или как окончательный идентификатор в разработаны-тип-
спецификатор
объявления шаблона класса друга, оно ссылается на сам шаблон класса. В противном случае это эквивалентно
к Имя Шаблона с последующим Шаблон-параметры шаблона класса, заключенного в <>,

То есть, bar<N - 2> находки bar как введенное имя класса ток класс (создание экземпляра). Как это используется с Шаблон-аргумент-список, это относится к другой, не связанной специализации bar, Имя введенного класса базового класса скрыто.

bar<0>::num доступ не через путь доступа, который проходит через частное наследование, но напрямую через имя введенного класса текущего класса, ссылаясь на сам шаблон класса. num быть публичным членом bar<0> доступно.

3

Другие решения

Для хорошего объяснения того, почему частное наследование не похоже на публичное и защищенное наследие, см. Два хороших ответа от частное наследство.

Исходя из общего понимания наследования, «частное наследование» C ++
это ужасный неправильный термин: это не наследство (насколько все
за пределами класса обеспокоен) но полная реализация
деталь класса.

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

Здесь есть одна оговорка: C ++ синтаксически воспринимает это как наследование,
со всеми вытекающими отсюда преимуществами и проблемами, такими как сфера охвата
видимость и доступность. Кроме того, C-стиль бросает (но не C ++
Cast!) фактически игнорирует видимость и таким образом преуспевает в
Производный указатель на базу:

Base* bPtr = (Base*) new Derived();

Излишне говорить, что это зло.


Публичное наследование означает, что все знают, что Derived является производным
с базы.

Защищенное наследование означает, что только Производные, друзья Производных и
классы, производные от Derived, знают, что Derived является производным от Base. *

Частное наследство означает, что только Производные и друзья Производных
знаю, что Derived является производным от Base.

Поскольку вы использовали частное наследование, ваша функция main () не имеет
Подсказка о выводе из базы, следовательно, не может назначить указатель.

Частное наследство обычно используется для выполнения
отношения «реализовано в терминах». Одним из примеров может быть то, что
Base предоставляет виртуальную функцию, которую нужно переопределить — и, таким образом,
должно быть унаследовано от — но вы не хотите, чтобы клиенты знали, что вы
иметь эти наследственные отношения.

Так же как Недоступный тип из-за частного наследования.

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

void foo(::A const& a) {}

Так, например, это будет работать:

class GrandChild;
struct GrandParent { enum {n = 0}; };

struct Parent : private GrandParent {
enum {n = 1};
friend GrandChild;
};

struct GrandChild : private Parent {
void f() {cout << GrandParent::n << endl;}
};

В противном случае вам нужно использовать глобальную область видимости или using директива принести ::GrandParent в сферу.

1

По вопросам рекламы [email protected]