2010-03-10 2 views
8

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

Я ценю, что 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; 
} 
+1

Добро пожаловать в StackOverflow! – fbrereto

+2

соблазн отредактировать комментарий «первый раз на stackoverflow», потому что вопрос красиво спросил;) –

+0

Теплый прием и отличные ответы. Огромное спасибо. – beldaz

ответ

4

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


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

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

В 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.

+0

* «разница между C++ 89» * -> * «разница между C++ 98» * –

+0

@gf: Исправлено. Спасибо. – AnT

+0

Только, что я был после, спасибо. Я думаю, что я буду использовать инструкцию 'using' для кода, над которым я работаю, для обеспечения совместимости со стандартом 98. – beldaz

1

В соответствии со стандартом:

9.2 Class members

1 [...] Members of a class are data members, member functions (9.3), nested types, and enumerators. Data members and member functions are static or non-static; see 9.4.Nested types are classes (9.1, 9.7) and enumerations (7.2) defined in the class, and arbitrary types declared as members by use of a typedef declaration (7.1.3).

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

  1. Is this standard behaviour? (a decent explanation of why would be very helpful)

No. По крайней мере, с 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. Can one expect it to work on most modern compilers (i.e., how portable is it)?

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

+0

+1 Хороший улов на PubDerived :: Nested :: foo(). Что касается указателя на стандарт, мне не было ясно, что было какое-то упоминание о том, что видел вложенный класс (ответ p00ya нашел важный оговорку, я думаю). – beldaz

0

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

+0

Только GCC, чтобы передать это, поэтому я был заинтересован в переносимости кода. Связанная запись в FAQ не содержит вложенных классов, насколько я могу видеть. – beldaz

1

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

Раздел 9.7:

A class defined within another is called a nested class. The name of a nested class is local to its enclosing class. The nested class is in the scope of its enclosing class. Except by using explicit pointers, references, and object names, declarations in a nested class can use only type names, static members, and enumerators from the enclosing class.

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

[...] If a class is declared to be a base class for another class using the private access specifier, the public and protected members of the base class are accessible as private members of the derived class.

9,9 Уплотненный Имена типов:

Type names obey exactly the same scope rules as other names.

Тогда в 11,8:

The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (11) shall be obeyed. The members of an enclosing class have no special access to members of a nested class; the usual access rules (11) shall be obeyed.

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

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

+0

Квоты, предоставленные вами, относятся к спецификации C++ 98. Это было переработано в C++ 03, поэтому вы увидите другое поведение в Комо и более поздние версии компиляторов GCC и MSVC++. – AnT

+0

+1 для ссылок на стандарт (замечание AndreyT также отмечено). Они поддерживают то, что я интуитивно ожидал. – beldaz

+0

Да, ничто из того, что я сказал, не может считаться применимым к C++ 03 (но у меня нет копии этой спецификации, к сожалению). – p00ya

2

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

В стандартах секции на C++ 98 и 2003 11.8.1 заявил: раздел

The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11) shall be obeyed. The members of an enclosing class have no special access to members of a nested class; the usual access rules (clause 11) shall be obeyed.

C++ 0x 11.8.1 говорит:

A nested class is a member and as such has the same access rights as any other member. The members of an enclosing class have no special access to members of a nested class; the usual access rules (Clause 11) shall be obeyed.

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

+0

Спасибо, что добавили свой ответ на мой относительно старый вопрос. Йоханнес связал DR, который вы упомянули в ответ на принятый ответ, но хорошо, что он включен непосредственно в ответ. +1 от меня. – beldaz

Смежные вопросы