Видимость конфиденциально наследованных определений типов к вложенным классам

В следующем примере (извинения за длину) я попытался изолировать некоторое неожиданное поведение, с которым я встретился, когда использование вложило классы в классе, который конфиденциально наследовался от другого. Я часто видел операторы о том, что нет ничего специального о вложенном классе по сравнению с невложенным классом, но в этом примере каждый видит, что вложенный класс (по крайней мере, согласно 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;
}
8
задан Piotr Chojnacki 15 February 2013 в 23:35
поделиться

4 ответа

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


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

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

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

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

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

Простой пример, иллюстрирующий разницу между 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.

4
ответ дан 5 December 2019 в 20:15
поделиться

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

0
ответ дан 5 December 2019 в 20:15
поделиться

Я сделал все возможное, чтобы собрать все соответствующие разделы из 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) недоступен").

1
ответ дан 5 December 2019 в 20:15
поделиться

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

9.2 Члены класса

1 [...] Члены класса являются членами данных, член функции (9.3), вложенные типы, и перечислители. Члены данных и функции-члены могут быть статическими или нестатическими; см. 9.4. Вложенные типы - это классы (9.1, 9.7) и перечисления (7.2), определенные в классе, и произвольные типы, объявленные как члены с помощью объявления typedef (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 static .

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

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

1
ответ дан 5 December 2019 в 20:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: