Видимость частных унаследованных typedefs для вложенных классов

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

Я ценю, что typdefs не то же самое, что данные членов, но я нашел это поведение удивительным, и я думаю, что многие другие тоже. Поэтому мой вопрос двоякий:--2-->

  1. это стандартное поведение? (достойное объяснение, почему было бы очень полезно)
  2. можно ли ожидать, что он будет работать на большинстве современных компиляторов (т. е. насколько он портативен)?

#include <iostream>

class Base {
  typedef int priv_t;
  priv_t priv;
public:
  typedef int pub_t;
  pub_t pub;
  Base() : priv(0), pub(1) {}
};

class PubDerived : public Base {
public:
  // Not allowed since Base::priv is private
  // void foo() {std::cout << priv << "n";}

  class Nested {
    // Not allowed since Nested has no access to PubDerived member data
    // void foo() {std::cout << pub << "n";}

    // Not allowed since typedef Base::priv_t is private
    // void bar() {priv_t x=0; std::cout << x << "n";}
  };

};

class PrivDerived : private Base {
public:
  // Allowed since Base::pub is public
  void foo() {std::cout << pub << "n";}

  class Nested {
  public:
    // Works (gcc 4.4 - see below)
    void fred() {pub_t x=0; std::cout << x << "n";}
  };
};

int main() {

  // Not allowed since typedef Base::priv_t private
  // std::cout << PubDerived::priv_t(0) << "n";

  // Allowed since typedef Base::pub_t is inaccessible
  std::cout << PubDerived::pub_t(0) << "n"; // Prints 0

  // Not allowed since typedef Base::pub_t is inaccessible
  //std::cout << PrivDerived::pub_t(0) << "n";

  // Works (gcc 4.4)
  PrivDerived::Nested o;
  o.fred(); // Prints 0
  return 0;
}

5 ответов


предисловие: в ответе ниже я ссылаюсь на некоторые различия между C++98 и c++03. Однако оказывается, что изменения, о которых я говорю, еще не вошли в стандарт, поэтому C++03 В этом отношении не отличается от C++98 (спасибо Йоханнесу за указание на это). Почему-то я был уверен, что видел его в C++03, но на самом деле его там нет. Тем не менее, проблема действительно существует (см. ссылку DR в комментарии Johannes), и некоторые компиляторы уже реализуют то, что они вероятно, рассмотрим наиболее разумное решение этого вопроса. Таким образом, ссылки на C++03 В приведенном ниже тексте неверны. Пожалуйста, интерпретируйте ссылки на C++03 Как ссылки на некоторые гипотетические, но очень вероятные будущие спецификации этого поведения, которые некоторые компиляторы уже пытаются реализовать.


важно отметить, что между стандартами C++98 и C++03 произошло значительное изменение прав доступа для вложенных классов.

В C++98 вложенный класс не имели особых прав доступа к членам объемлющего класса. Это был в основном полностью независимый класс, только что объявленный в рамках закрытого класса. Он мог только получить доступ общественные члены включающего класса.

В C++03 вложенному классу были предоставлены права доступа к членам заключительного класса как включающего класса. Точнее, вложенному классу были предоставлены те же права доступа как статический член функция включающего класса. Т. е. теперь вложенный класс может получить доступ к любой члены заключительного класса, включая частная те.

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

конечно, вы должны помнить, что объект вложенного класса не привязаны к каким-либо конкретным объектом включающий класс. Что касается реальных объектов, то это два независимых класса. Для доступа к нестатическим элементам данных или методам заключающего класса из вложенного класса необходимо иметь определенный объект заключающего класса. Другими словами, снова вложенный класс действительно ведет себя так же, как статическая функция-член объемлющего класса: он не имеет конкретного this указатель для включающего класса, поэтому он не может получить доступ к нестатическим члены заключительного класса, если вы не приложите усилий, чтобы предоставить ему доступ к определенному объекту заключительного класса. Без него вложенный класс может получить доступ только к typedef-именам, перечислениям и статическим членам заключающего класса.

простой пример, иллюстрирующий разницу между C++98 и c++03, может выглядеть следующим образом

class E {
  enum Foo { A };
public:
  enum Bar { B };

  class I {
    Foo i; // OK in C++03, error in C++98
    Bar j; // OK in C++03, OK in C++98
  };
};

это изменение именно то, что позволяет ваш


короткий ответ: вложенные классы имеют доступ к содержащим классам private member в C++0x, но не C++1998 и c++2003. Однако это legal для компиляторов C++98 и c++2003 поддерживается поведение C++0x, так как старое поведение считается дефектом.

в разделе 11.8.1 стандартов C++98 и 2003 указано:

члены вложенного класса не имеют специальный доступ к членам включающего класса или классов или функции что даровали дружбу к заключительному классу; обычный правила доступа (статья 11) повиновался. Члены прилагающего класс не имеет специального доступа к члены вложенного класса; обычный правила доступа (статья 11) повиновался.

C++0x раздел 11.8.1 гласит:

вложенный класс является членом и как таковой имеет те же права доступа, что и любой другой участник. Члены объемлющего класса не имеют специального доступа к членам вложенного класса; обычные правила доступа (Статья 11) будьте послушны.

Отчет О Дефекте Основного Языка 45 показывает, что исходное поведение считалось дефектом, поэтому законно, чтобы компиляторы, отличные от C++0x, также поддерживали новое поведение, хотя это и не требуется.


на:

9.2 членов класса

1 [...] Члены класса данные-члены, функции-члены (9.3), вложенные типы, и перечислители. Данные-члены и функции-члены, статические и нестатические; см. 9.4.Вложенные типы классы (9.1, 9.7) и перечисления (7.2), определенные в классе, и произвольные типы, объявленные в качестве членов с помощью декларации typedef (7.1.3).

для отвечайте на вопросы:

  1. это стандартное поведение? (достойное объяснение того, почему очень полезно)

нет. По крайней мере, с typedefs недоступен. Однако, обратите внимание, что:

class Nested {
    // Not allowed since Nested has no access to PubDerived member data
     void foo() {std::cout << pub << "\n";}

проблематично. Вложенный класс не имеет экземпляров PubDerived для работы с ни pub a static объект-членов.

  1. можно ли ожидать, что он будет работать на самых современных компиляторы (т. е. как портативный это)?

да. Но проверьте документацию на соответствие стандартам. И всегда: попробуйте с несколькими компиляторами, такими как Comeau, в строгом режиме.


Я сделал все возможное, чтобы собрать все соответствующие положения из ISO/IEC 14882:1997.

9.7:

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

11.2.1 (должно быть вполне очевидно):

[...] Если класс объявлен базовым классом для другого класса с помощью спецификатора закрытого доступа, открытые и защищенные члены базового класса доступны как частные члены производного класса.

9.9 Имена Вложенных Типов:

имена типов подчиняются точно таким же правилам, как и другие имена.

затем в 11.8:

члены вложенного класса не имеют специального доступа ни к членам заключающего класса, ни к классам или функциям, которые предоставили дружбу заключающему классу; должны соблюдаться обычные правила доступа (11). Члены заключительного класса не имеют специального доступа к членам вложенного класса; должны соблюдаться обычные правила доступа (11).

однако Comeau C++, который, кажется, имеет лучшую стандартную поддержку, имеет то же поведение, что и GCC (позволяет fred запрещает bar С "error: type" Base:: priv_t" (объявлено в строке 4) недоступно").


Это не ответ на ваш вопрос, но по моему чтение в C++ чаво Лайт 24.6 то, что вы пытаетесь сделать, не допущено. Я не уверен, почему gcc разрешает это; вы пробовали это и в других компиляторах?