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
, то потребуется включить через #includ
e заголовок