Вопрос: Почему декларация и определение определены таким образом в Effective C ++?


В Эффективный C ++  (3-е изд.), Пункт 2 (Предпочитают const, enum а также inline в #define), сегмент кода для констант, специфичных для класса, читается:

class GamePlayer {
private:
    static const int NumTurns = 5;    // constant declaration
    int scores[NumTurns];             // use of constant
    ...
};

Затем в книге говорится (по-моему), что static const int NumTurns = 5; не является определением, которое обычно требуется для C ++ для членов класса, если оно не является статической интегральной константой, адрес которой никогда не используется. Если приведенное выше значение неверно для константы или если компилятор настаивает на определении по любой причине, определение должно быть представлено в файле реализации следующим образом:

const int GamePlayer::NumTurns;    // definition of NumTurns; see
                                   // below for why no value is given

Согласно книге (также в моих собственных словах), в определении нет значения, поскольку оно уже указано в декларации.

Это путает то, что, как я думал, я уже знаю об определениях декларации и определения (и я дважды проверял на Google, прежде чем задавать этот вопрос):

  • Почему static const int NumTurns = 5 не определение? Является NumTurns не инициализируется значением 5 здесь, и не так ли, когда декларация и определение происходят вместе, это называется инициализацией?
  • Почему static интегральные константы не требуют определения?
  • Почему второй фрагмент кода считается определением, когда значение не определено, но декларация внутри класса, которая содержит это значение, по-прежнему не является одной (по существу, возвращается к моему первому вопросу)?
  • Не является ли инициализация определением? Почему здесь не нарушается правило «только одного определения»?

Возможно, я просто путаюсь здесь здесь, поэтому кто-то любезно перевоспитает меня с нуля: почему эти две строки объявлений и определений кода, а не другие, и есть ли там какие-либо примеры инициализации? Инициализация также является определением?

Кредит: фрагменты кода напрямую цитируются в книге.

редактировать : С дополнительной ссылкой на В чем разница между определением и декларацией?

  • В декларации вводится идентификатор и тип
  • Определение создает и реализует

Итак, да ... это не похоже на то, что происходит здесь.

Изменить 2 : Я считаю, что компиляторы могут оптимизировать статическую интегральную константу, не сохраняя ее в памяти и просто заменяя ее встроенным в код. Но если NumTurns используется адрес, почему декларация не меняется в определение объявления + автоматически, поскольку экземпляр уже существует?

Редактировать 3 : (Это редактирование имеет меньше общего с исходным вопросом, но по-прежнему остается выдающимся от него. Я размещаю его здесь, так что мне не нужно копировать-вставлять комментарии в каждый ответ ниже. Пожалуйста, ответьте мне для этого в комментариях . Благодаря!)

Спасибо за ответы. Теперь моя голова намного яснее, но последний вопрос из Edit 2 все еще остается: если компилятор обнаруживает условия в программе, где требуется определение (например. &NumTurns используется в программе), почему он не просто автоматически переинтерпретирует static const int NumTurns = 5;как объявление и определение вместо объявления только? Он имеет все синтаксис определения в любом месте в программе.

Я исхожу из фона Java в школе и не нуждаюсь в том, чтобы сделать отдельные определения, подобные этим для статических членов, в соответствии с вышеизложенным способом. Я знаю, что C ++ отличается, но я хочу знать, почему это так. Статический интегральный член, заменяемый inline, если адрес никогда не используется, скорее похож на оптимизацию на меня, чем на фундаментальную функцию, поэтому почему мне нужно ее обойти (предоставление отдельного утверждения в качестве определения, когда условия не являются несмотря на то, что синтаксис исходного утверждения достаточен), а не наоборот: компилятор, рассматривающий исходный оператор как определение, когда требуется иметь его с синтаксисом, достаточен)?


21


источник


Ответы:


Отказ от ответственности: я не являюсь стандартным гуру.

Предлагаю вам прочитать следующие два:

http://www.stroustrup.com/bs_faq2.html#in-class
а также:
В чем разница между определением и декларацией?

Почему static const int NumTurns = 5 не является определением? Является ли NumTurns не   здесь инициализировано значение 5, и не так ли, когда   декларация и определение происходит вместе, это называется   инициализация?

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

Строка кода

static const int NumTurns = 5;

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

const int MyClass::NumTurns;  

Цитата Бьярне:

Вы можете взять адрес статического члена, если (и только если) он имеет   внеклассное определение:

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Почему статические интегральные константы не требуют определения?

Они делают, если вы хотите принять их адреса.

Не является ли инициализация определением?

Нет. Инициализация - это акт установки начальных значений в некоторой сущности (примитив, объект).

void someFunc (){
  int x; //x is declared, but not initialized
  int y = 0; //y is declared+ initialized.
}

Почему здесь не нарушается правило «только одного определения»?

Почему? У вас все еще есть NumTurns во всем коде. Символ MyClas::NumTurns появляется только один раз. Определение только делает переменную отображаемой в памяти программы. Правило ODR указывает, что любой символ может быть объявлен только один раз.

РЕДАКТИРОВАТЬ: Вопрос в основном сводится к «Почему сам компилятор не может решить самостоятельно?» Я не являюсь членом комита, поэтому я не могу дать полностью законный ответ.
Мое умное предположение заключается в том, что позволить компилятору решить, что это не философия C ++. когда вы разрешаете компилятору, например, где объявить статический интеграл const, Я, как разработчик, могу затронуть следующие проблемы:

1) что произойдет, если мой код является библиотекой только для заголовков? где должен ли мой компилятор объявить переменную? в первом cpp-файле он встречается? в файле, который содержит основной?

2) если компилятор решает, где объявить переменную, есть ли у меня какая-либо гарантия что эта переменная будет объявлена ​​вместе с другими статическими переменными (которые я указал вручную), таким образом сохраняя местонахождение кеша плотно?

все больше вопросов возникает, как только мы одобряем мышление «пусть компилятор будет одинок», Я думаю, что это фундаментальное различие между Java и C ++.

Java: пусть JVM делает свою харистику.
C ++: пусть разработчик делает свое профилирование.

в конечном счете, на C ++ компилятор проверяет, что все делает смысл и превращает код в двоичный, не выполнять работу, а не разработчика.

* или байт-код, если вы используете управляемый C ++ (boo ...)


13



Одним из последствий правила одно определение является то, что статический член класса может иметь только одно определение. Однако, если несколько единиц компиляции определяют его, будет одно определение для каждой единицы компиляции в рамках программы. Чистый эффект заключается в том, что декларация не может быть определением без нарушения правила единого определения. На практике компоновщики обычно не достаточно умны, чтобы разрешать такие множественные определения.

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

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

#include <iostream>
int main()
{
     int x;   //  declaration and definition of x

     std::cout << x << '\n';    // undefined behaviour as x is uninitialised

     x = 42;   // since x is not yet initialised, this assignment has an effect of initialising it

     std::cout << x << '\n';    // OK as x is now initialised
}

На практике инициализация может быть частью декларации, но не обязательна.

Отредактировано для ответа на «Редактировать 3» в исходном вопросе:

C ++ имеет отдельную модель компиляции. Модель Java опирается на возможности (более умный компоновщик, привязка времени выполнения), который не имеет модели C ++. В C ++, если один блок компиляции видит объявление, но не имеет определения, компилятор просто предполагает, что определение находится в другом компиляционном блоке. Обычно (с большим количеством цепочек построения) линкер позже обнаруживает, что необходимое определение не существует, поэтому этап связывания не выполняется. И наоборот, если бы каждая единица компиляции, которая нуждалась в определении, чтобы существовать, фактически создала ее, компилятор нарушил бы «одно правило определения» и типичный немой компоновщик, который, среди прочего, недостаточно умен, чтобы свернуть что-то определенное несколько раз в одно определение - будет жаловаться на многократно определенный символ.


7



Почему static const NumTurns = 5 не является определением? Является ли NumTurns не инициализированным значением 5 здесь, и не так ли, когда декларация и определение происходят вместе, она называется инициализацией?

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

Поскольку член «определен» внутри декларации класса (т. Е. В этой точке не создается экземпляр класса), то это объявление.

Знак равенства, на который вы полагаетесь, чтобы назвать это определение, является просто значением по умолчанию для элемента struct, а не инициализацией.

Почему статические интегральные константы не требуют определения?

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

Почему второй фрагмент кода считается определением, когда значение не определено, но декларация внутри класса, которая содержит это значение, по-прежнему не является одной (по существу, возвращается к моему первому вопросу)?

Как я уже говорил, это потому, что второй код выделяет память для переменной.

Не является ли инициализация определением? Почему здесь не нарушается правило «только одного определения»?

Поскольку инициализация не является определением.

Если компилятор обнаруживает условия в программе, где требуется определение (например, & NumTurns используется в программе), почему он не просто автоматически переинтерпретирует статический const int NumTurns = 5; как объявление и определение вместо объявления только?

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

Чтобы избежать этой проблемы, вы делаете это определение вручную. Вы определяете его внутри файла cpp (т. Е. - не в заголовке), тем самым разрешая выделение одному конкретному файлу, который линкер может взорвать.


5