std::string s = 'Kangaroo';
// main.cpp
#include <iostream>
#include 'global.h'
using namespace std;
int main() {
cout << 'x = ' << x << endl;
cout << 's = ' << s << endl;
}
Ключевое слово extern — это способ сказать компилятору, что реальная область памяти для переменной выделяется в другом месте, extern говорит компоновщику, что переменная описана где-то в другом объектном файле и что компоновщик должен найти ее при создании конечного исполняемого файла или библиотеки. Если компоновщик не находит переменной, объявленной как extern, или если он находит более одного ее определения, он генерирует ошибку компоновки.
Пример 2.3 не слишком впечатляет, но он хорошо иллюстрирует вопрос. Две мои глобальные переменные объявляются в
int x = 7;
std::string s = 'Kangaroo';
Мне требуется доступ к ним из других файлов реализации, так что я поместил в заголовочный файл extern для них:
extern int x;
extern std::string s;
Разница между объявлением и определением очень важна. В C++ можно объявить что-либо несколько раз, при условии совпадения объявлений, но определить что-либо можно только один раз. Это называется extern — это механизм, позволяющий сказать компилятору и компоновщику, что определение находится где-то еще и что оно должно быть разрешено при компоновке.
Нельзя сказать, что использование extern должно быть постоянным. Его следует использовать обдуманно и только тогда, когда это необходимо, так как оно позволяет создавать переменные, глобальные для всего приложения. Иногда оно может потребоваться для поистине глобальных объектов или данных — объекта журналирования, оборудования, большого объекта общих данных, но в большинстве случаев имеются более адекватные альтернативы.
2.3. Снижение числа #include с помощью предварительного объявления классов
Имеется заголовочный файл, который ссылается на классы из других заголовочных файлов, и требуется снизить зависимости компиляции (и, возможно, время).
Чтобы избежать ненужных зависимостей при компиляции, везде, где только возможно, используйте предварительное объявление классов. Пример 2.4. является коротким примером предварительного объявления класса.
// myheader.h
#ifndef MYHEADER_H__
#define MYHEADER_H__
class A; // заголовок для А включать не требуется
class В {
public:
void f(const A& a);
// ...
private:
A* a_;
};
#endif
Где-то в другом месте имеется заголовочный файл и, вероятно, файл реализации, который объявляет и определяет класс А, но в файле А меня не волнуют: все, что мне требуется знать, — это то, что А — это класс.
Предварительное объявление класса — это способ игнорировать подробности, о которых не требуется беспокоиться. В примере 2.4 А ничего, кроме того, что он существует и что это класс.
Рассмотрим, что случится, если с помощью #include включить заголовочный файл для А, или, что более реально, заголовочный файл для полудюжины или более классов, присутствующих в реальном заголовочном файле. Тогда файл реализации (
Создайте предварительное объявление класса, и эти зависимости компиляции исчезнут. Использование предварительного объявления просто создает имя, на которое можно ссылаться далее в заголовочном файле. Компоновщик должен будет сам найти в файлах реализаций подходящее определение.
К несчастью, использовать предварительное объявление можно не всегда. Класс В в примере 2.4 использует только указатели или ссылки на A, так что ему достаточно только предварительного объявления. Однако если бы в определении класса В я использовал функцию-член (метод) или переменную А или если бы создавал объект типа А, а не только указатель или ссылку на него, то предварительного объявления окажется недостаточно. Причиной этого является то, что файлы, включающие В, и если A является членом В, то компилятор, чтобы определить размер В, должен знать размер А. Указатель или ссылка на что-либо всегда имеют один и тот же размер, так что в случае использования указателей или ссылок подробности об А компилятор не интересуют, и, следовательно, заголовочный файл не требуется
Неудивительно, что если включить в A, то потребуется включить через #include заголовок
