Видимость приватно унаследованных typedef для вложенных классов - PullRequest
8 голосов
/ 11 марта 2010

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

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

  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 ]

4 голосов
/ 11 марта 2010

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


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

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

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

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

Конечно, вы должны помнить, что объект вложенного класса никоим образом не связан с каким-либо конкретным объектом включающего класса. Что касается реальных объектов, то это два независимых класса. Чтобы получить доступ к нестатическим членам данных или методам включающего класса из вложенного класса, у вас должен быть определенный объект включающего класса. Другими словами, еще раз, вложенный класс действительно ведет себя так же, как статическая функция-член включающего класса: у него нет определенного указателя 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
  };
};

Это изменение именно то, что позволяет вашей функции PrivDerived::Nested::fred компилироваться. Он не пройдет компиляцию в педантичном компиляторе C ++ 98.

2 голосов
/ 24 декабря 2010

Краткий ответ: вложенные классы имеют доступ к закрытому члену содержащихся классов в C ++ 0x, но не в C ++ 1998 и C ++ 2003. Однако допустимо для компиляторов C ++ 98 и C ++ 2003 поддерживает поведение C ++ 0x, поскольку старое поведение считается дефектом.

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

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

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

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

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

1 голос
/ 11 марта 2010

Я приложил все усилия, чтобы собрать все соответствующие пункты из ИСО / МЭК 14882: 1997.

Раздел 9.7:

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

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

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

9,9 Имена вложенных типов:

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

Затем в 11.8:

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

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

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

1 голос
/ 11 марта 2010

Согласно стандарту:

9,2 Члены класса

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

Чтобы ответить на ваши вопросы:

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

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

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, в строгом режиме.

0 голосов
/ 11 марта 2010

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

...