При наследовании от базового класса, что происходит с вложенными классами?

Скажем, у меня есть эти классы:

class Base
{
    public:

        class Foo { ... };

        ...
};

Затем из базы получается следующий класс:

class Derived : public Base
{
    // no mention or redefinition of nested class "Foo" anywhere in "Derived"
};

Означает ли это, что теперь у нас есть отдельный Derived::Foo, или Derived::Foo точный, как Base::Foo?

Вот пример этого сценария: что, если кто-то выбрал экземпляр Derived::Foo? Будет ли это обнаружено в этом сценарии:

catch ( const Base::Foo &ex )
{
    // would this also catch an instance of Derived::Foo?
}

Ответ 1

Base::Foo и Derived::Foo действительно являются одним и тем же типом, класс является просто составным типом (из черновик стандарта С++ 3.9.2), и мы не ожидали, что тип, унаследованный от базового класса, будет другим типом в производном классе. Например, если Base содержит:

typedef int newType1 ;

пока Derived не обновил newType1, тогда мы ожидали бы, что Base::newType1 и Derived::newType1 будут одного и того же типа, а вложенный класс не отличается. Если мы ссылаемся на проект стандартного раздела 9.2 Члены класса, параграф 1, говорит (внимание мое):

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

Это подтверждает, что интуитивные вложенные классы - это просто типы (а также члены), для раздела сателлита без поддержки 9.7, на который ссылаются выше, находится раздел вложенного класса и из раздела 10 Производные классы, параграф 1, мы видим:

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

Так как они одного типа, catch будет работать нормально.

Ответ 2

Derived::Foo просто обращается к Base::Foo, поэтому это всего лишь два способа обращения к одному типу. Вы можете легко проверить это с помощью std::is_same:

#include <type_traits>

struct Base
{
    class Foo {};
};

struct Derived : Base {};

static_assert( std::is_same< Base::Foo, Derived::Foo >::value, "Oops" );

int main()
{
    try {
        throw Derived::Foo();
    }
    catch( const Base::Foo& ) {}
}

Как вы можете видеть, это также означает бросать его одним именем и ловить его другими работами.

Пример в реальном времени