2016-03-10 6 views
17

Следующий простой фрагмент кода компилирует, хотя я не понимаю, почему:C++ вперед Объявление классов в классах

class C { 
    class B; 

    class A { 
     B getB() { return B(); } 
    }; 

    class B { 
    }; 
}; 

int main(int, char**) 
{ 
    return 0; 
} 

Если я тогда закомментировать «class C» вещи, так что вперед декларация B определение A и определение B больше не являются вложенным внутри класса, код не компилируется, поскольку B является неполным типа:

main.cpp: In member function 'B A::getB()': 
main.cpp:6: error: return type 'struct B' is incomplete 
main.cpp:6: error: invalid use of incomplete type 'struct B' 
main.cpp:3: error: forward declaration of 'struct B' 

Я underst и что это означает, что тип является неполным, а именно, что он еще не определен, и поэтому компилятор не может знать, сколько места выделяется для него. Но почему B считается недоработанным в коде выше, где A и B оба объявлены и определены внутри C?

ответ

10

Я считаю, что это является следствием [basic.scope.class]:

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

То есть, объем полный декларации B включает в себя тело функции члена вложенного класса:

class C { 
    class B; // (1) 

    class A { 
     B getB() { 
      return B(); // both (1) and (2) in scope here 
         // since (2) is the complete type declaration, 
         // this is perfectly fine 
     } 
    }; 

    class B { // (2) 
    }; 
}; 

По сравнению, если C было пространство имен вместо класса , объем полной декларации класса B не будет распространяться на A::getB(). Единственной видимой декларацией было бы объявление вперед B, которое я обозначил (1) - так что B() было бы построением неполного типа.

+0

Удивительный, спасибо, что очистили это. Что вызвало этот конкретный вопрос - это класс libpqxx ['result'] (http://pqxx.org/devprojects/libpqxx/doc/stable/html/Reference/), который имеет вложенные классы' tuple' и 'field', в который 'tuple' строит« поле »в одном из своих тел функции (а именно, строка 183) – villapx

5

В стандарте указано, что тело метода интерпретируется после класса, который его охватывает.

Таким образом, во время оценки тела C::A::getB(), A, B и C все полные типы.

+0

Любой шанс, что вы могли бы знать, где я мог бы найти это словоблудие? – villapx

+1

, пытаясь его найти ... к сожалению, термин «функция-член» упоминается 582 раза в проекте стандартного PDF-документа 2015 года ... http://open-std.org/JTC1/SC22/WG21/docs/papers/2015 /n4527.pdf –

9

Тело встроенной функции члена не обрабатывается до тех пор, пока определение класса не будет полностью обработано.

Таким образом, вы можете использовать:

class A 
{ 
    class B; 
    B getB() { return B(); } 

    class B {}; 
}; 

Это также позволяет переменные-члены, которые еще не объявлены, которые будут использоваться в инлайн определения функции члена.

class Foo 
{ 
    int getBar() { return bar; } 

    int bar; 
}; 

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

PS Я не могу быстро найти ссылку в стандарте, подтверждающем мое требование.

PS 2The answer by Barry имеет ссылку в стандарте, что делает код в вопросе действительным.

+0

Вы избили меня до ответа. Я пытался найти постоянных клиентов. Я думаю, что он конвертируется * Аналогично во время поиска имени, когда unqualified-id (5.1), используемый в определении , функция-член для класса X разрешает статический член, перечислитель или вложенный тип класса X или базовый класс X, unqualified-id преобразуется в идентификатор (5.1), в котором спецификатор вложенных имен называет класс функции-члена. * From ** [class.mfct.non-static] **. Если вы согласитесь, продолжайте использовать его. – NathanOliver

+0

@NathanOliver Я не думаю, что это правильно. Это просто говорит о том, что 'B' будет найдено, но' B' будет найден независимо от форвардной декларации. Я не знаю, где находится правая секция, и не может найти ее. – Barry

+0

Возможно * Если класс X определен в области пространства имен, вложенный класс Y может быть объявлен в классе X, а позже определен в определении класса Xили позже определен в области пространства имен, охватывающей определение класса X. * из ** [class.nest] ** – NathanOliver

0

Кроме того, когда у меня есть необходимость перенаправлять объявлять вложенные классы Склоняюсь запах какой-то плохой дизайн в моем коде, трюк я использую:

// Foo.h 
class Foo { 
    class Bar { 
    }; 
}; 
class Foobar : public Foo::Bar {}; 


// Zoo.h 
/* Fwd declare */ 
class FooBar; 
+0

Вы можете отредактировать свой ответ – villapx

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