c порядок определений в классе, сюрприз

#c

#c

Вопрос:

Страуструп утверждает в книге по языку C , что порядок определений в классе не имеет значения.
Действительно:

 class C1 { 
       int foo() { return bar(); }    // where is bar() ?
       int bar() { return m_count; }  // oh, here is bar(). but where is m_count ?
       int m_count;                   // here is m_count. Better late than never !
}
  

Это компилируется. Несмотря на неправильный порядок. Как и было обещано.
Пока все хорошо.

Однако это не компилируется:

 class C2 {
      void baz(Inner *p) {} // we were promised that order does not matter
                            // is Inner defined ?
      struct Inner {};      // yes, Inner is define here.
};
  

Это выглядит противоречащим обещанию Страуструпа о свободном упорядочении внутри класса. Цитирую Страуструпа: «Функция-член, объявленная в классе, может ссылаться на каждого члена класса, как если бы класс был полностью определен до рассмотрения тел функций-членов».

Кто-нибудь знает предложение ref to standard, которое разрешает C1 и запрещает C2? Мне любопытно, почему C2 был запрещен, в то время как C1 разрешен. Может быть, это ошибка компилятора, которая противоречит стандарту?

Комментарии:

1. Я думаю, вам нужно процитировать Страуструпа слово в слово, если вы хотите, чтобы мы решили эту проблему. Что именно он написал?

2. ТониК. Страуструп пишет: «Функция-член, объявленная в классе, может ссылаться на каждого члена класса, как если бы класс был полностью определен до рассмотрения тел функций-членов».

3. В этом случае кажется, что утверждение Страуступа ложно (хотя, возможно, оно было истинным, когда он его писал). В проекте нового стандарта C 0x говорится: «Членами класса являются элементы данных, функции-члены, вложенные типы и перечислители».

4. Вот почему я спросил … Я почти уверен, что все разговоры об объявлениях и определениях относятся к какому-то другому вопросу.

5. Мне казалось, что намерением разработчиков языка было сделать это так, как в обещании Страуструпа. Разработчики компилятора испортили это, вероятно, по причине, упомянутой byn.m. Или они недостаточно тщательно стандартизировали :-; Я тестировал на C 03. Возможно, это исправлено в C 0x.

Ответ №1:

Обратите внимание, что следующее отлично компилируется (в VS2008):

 class C2 {
  void baz() {  Inner* i = new Inner(); }
  struct Inner {}; 
};
  

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

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

Ответ №2:

Идентификатор, который обозначает тип, должен быть объявлен перед использованием. Это связано со сложной структурой грамматики C . Компилятор просто не сможет проанализировать определенный код, если он заранее не знает, какие идентификаторы являются типами.

Ответ №3:

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

Комментарии:

1. И прямое объявление помогает только в том случае, если оно используется в качестве указателя или ссылки, в противном случае компилятору требуется полное определение типа

2. Это вообще не ответ. Как m_count, так и Inner не имеют предварительного объявления. Тем не менее, один допускает использование перед объявлением, а другой — нет. Технически, «int m_count;» и «struct Inner {}» являются обоими определениями. Можете ли вы объяснить, почему одно может появиться после использования, а другое нет? Оба не имеют предыдущего объявления?

3. Некоторые объявления также являются определениями, что действительно все запутывает. class A; и extern int m_count; являются всего лишь объявлениями. Класс должен быть объявлен, прежде чем его можно будет использовать в определении другого класса.

4. Имейте в виду, что ваши встроенные функции являются определениями функций. Вы можете сначала объявить функции, а затем определить их.

5. Для rubenvb: На самом деле, в приведенном случае, прямое объявление — это все, что требуется.

Ответ №4:

Порядок определения не имеет значения, но порядок объявления имеет значение.

В C объявление означает (грубо говоря) указание «вида» или type символа:

  • class A; —> A имеет вид «класс»
  • void foo(int); —> foo — это функция, которая принимает значение int и ничего не возвращает

Определение, однако, полностью определяет, к чему относится символ. Как и в C, определение также является объявлением.

Наконец, чтобы еще больше запутать ситуацию, для типов, объявленных пользователем, иногда требуется определение (complete-type), в то время как иногда достаточно простого объявления…

Теперь давайте исправим вашу проблему, я проиллюстрирую это простым примером с использованием C 0x:

 struct A {
  void foo() {   count; }
  void bar(decltype(count) c); // error: 'count' was not declared in this scope

  int count;
};

struct B {
  void foo() { Inner a; a.foo(); }
  void bar(Inneramp; i); // error: 'Inner' has not been declared

  struct Inner { void foo(); };
};
  

Как вы можете заметить, A::foo анализируется правильно, в то время как A::bar это не так.

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

Можно сказать, что для компилятора код эквивалентен:

 struct A {
  void foo();
  void bar(decltype(count) c); // error: 'count' was not declared in this scope

  int count;
};
void A::foo() {   count; }

struct B {
  void foo();
  void bar(Inneramp; i); // error: 'Inner' has not been declared

  struct Inner { void foo(); };
};
void B::foo() { Inner a; a.foo(); }
  

Предложение Страуструпа «простое», но не обязательно точное. Например, использование Inner в качестве атрибута требует, чтобы он был полностью определен (в качестве нестатических атрибутов могут использоваться только полные типы), что может привести к дальнейшим неприятностям.

 struct C {
  struct Inner;

  Inner foo; // error: field 'foo' has incomplete type

  struct Inner { };
};
  

Хотя можно было бы возразить, что Inner foo это объявление и, следовательно, на него не распространяется цитата Страуструпа.

Комментарии:

1. «принимает значение int и ничего не возвращает» — какой грубый и жадный foo! Помимо этого, это именно то, что я хотел бы написать, если бы мой iPhone не заглох при вводе ответа, 1.

Ответ №5:

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