Подтвердить что ты не робот

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

В Effective С++ (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; не является определением, которое обычно требуется для С++ для членов класса, если оно не является статической интегральной константой, адрес которой никогда не используется. Если приведенное выше значение неверно для константы или если компилятор настаивает на определении по какой-либо причине, определение должно быть представлено в файле реализации следующим образом:

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), почему это не просто автоматически reinterpret static const int NumTurns = 5; как объявление и определение вместо объявления только? У этого синтаксиса есть определение где-нибудь еще в программе.

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

4b9b3361

Ответ 1

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

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

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 указывает, что любой символ может быть объявлен только один раз.

EDIT: Вопрос в основном сводится к "Почему сам компилятор не может решить самостоятельно?" Я не являюсь членом комита, поэтому я не могу дать полностью законный ответ.
Мое умное предположение заключается в том, что позволить компилятору решить, что это не философия С++. когда вы разрешаете компилятору, например, где объявить статический интеграл const, Я, как разработчик, могу затронуть следующие проблемы:

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

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

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

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

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

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

Ответ 2

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

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

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

#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" в исходном вопросе:

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

Ответ 3

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

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

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

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

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

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

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

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

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

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

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

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

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