В следующем примере (извинения за длину) я попытался изолировать некоторое неожиданное поведение, с которым я встретился, когда использование вложило классы в классе, который конфиденциально наследовался от другого. Я часто видел операторы о том, что нет ничего специального о вложенном классе по сравнению с невложенным классом, но в этом примере каждый видит, что вложенный класс (по крайней мере, согласно GCC 4.4) видит общедоступные определения типов класса, который конфиденциально наследован заключительным классом.
Я ценю, что typdefs не являются тем же как членскими данными, но я нашел это удивление поведения, и я предполагаю, что многие другие были бы, также. Таким образом, мой вопрос является двукратным:
#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;
}
Предисловие: В ответе ниже я ссылаюсь на некоторые различия между 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.
Это не ответ на ваш вопрос, но, согласно моему прочтению C ++ FAQ Lite 24.6 , то, что вы пытаетесь сделать, не не допускается. Я не уверен, почему gcc это разрешает; пробовали ли вы это и в других компиляторах?
Я сделал все возможное, чтобы собрать все соответствующие разделы из 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) недоступен").
Согласно стандарту:
9.2 Члены класса
1 [...] Члены класса являются членами данных, член функции (9.3), вложенные типы, и перечислители. Члены данных и функции-члены могут быть статическими или нестатическими; см. 9.4. Вложенные типы - это классы (9.1, 9.7) и перечисления (7.2), определенные в классе, и произвольные типы, объявленные как члены с помощью объявления typedef (7.1.3).
Чтобы ответить на ваши вопросы:
- Это стандартное поведение? (достойное объяснение, почему было бы очень полезно)
Нет. По крайней мере, с typedef
недоступны. Однако учтите, что:
class Nested {
// Not allowed since Nested has no access to PubDerived member data
void foo() {std::cout << pub << "\n";}
проблематично. Вложенный класс не имеет экземпляра PubDerived
для работы и не является объектом-членом pub
static
.
- Можно ли ожидать, что он будет работать с большинством современных компиляторов (т.е. насколько он переносим )?
Да. Но обязательно проверьте документацию на соответствие стандартам. И всегда: попробуйте использовать несколько компиляторов, например Comeau, в строгом режиме.