Урок №23. Header guards и #pragma once



Книга Урок №23. Header guards и #pragma once

В данном занятии мы изучим концепцию header guards и директиву pragma once в языке программирования C++. Рассмотрим их назначение, необходимость и правильное применение.

Проблема дублирования объявлений

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

int main ( )

{

int x ; // это объявление идентификатора x

int x ; // ошибка компиляции: дублирование объявлений

return 0

}

То же самое относится и к возможностям:

int boo ( )

{

return 7 ;

}

int boo ( ) // ошибка компиляции: дублирование определений

{

return 7 ;

}

int main ( )

{

std :: cout << boo ( ) ;

return 0 ;

}

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

Давайте изучим следующий код:

math.h:

int getSquareSides ( )

{

return 4 ;

}

Файл main.cpp:

#include "math.h"

#include "geometry.h"

int main ( )

{

return 0 ;

}

Эта, казалось бы, безобидная программа, не сможет быть скомпилирована! Затруднение заключается в определении функции в файле math.h. Давайте внимательно изучим, что происходит:

В начале main.cpp происходит подключение заголовочного файла math.h, что приводит к тому, что определение функции getSquareSides копируется в main.cpp.

После этого main.cpp включает в себя заголовочный файл geometry.h, который в свою очередь включает math.h.

Функция getSquareSides, которая находится в geometry.h и уже второй раз копируется в main.cpp, является дубликатом изначальной версии из файла math.h.

По завершении всех инструкций include, содержимое main.cpp будет иметь следующий вид:

int getSquareSides ( ) // из math.h

{

return 4 ;

}

int getSquareSides ( ) // из geometry.h

{

return 4 ;

}

int main ( )

{

return 0 ;

}

При использовании двух заголовочных файлов с одинаковым определением функции в main.cpp возникает дублирование определений и ошибка компиляции. В отдельных файлах ошибок нет, но при подключении их в main.cpp возникают проблемы. Например, если в geometry.h требуется функция getSquareSides(), а в main.cpp нужны и geometry.h, и math.h, как быть? Какое решение можно найти в данной ситуации?

Header guards

Для решения данной проблемы достаточно применить механизм header guards (защиты от множественного включения в языке программирования C++). Header guards представляют собой условные директивы компиляции, которые включают в себя следующее:

#ifndef SOME_UNIQUE_NAME_HERE

#define SOME_UNIQUE_NAME_HERE

// Основная часть кода

#endif

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

Для обеспечения уникальности ваших заголовочных файлов необходимо использовать header guards. SOME_UNIQUE_NAME_HERE может быть произвольным идентификатором, однако обычно в качестве идентификатора используется имя заголовочного файла с суффиксом _H. Например, в файле math.h идентификатор будет MATH_H:

Header file math.h:

#ifndef MATH_H

#define MATH_H

int getSquareSides ( )

{

return 4 ;

}

#endif

Даже заголовочные файлы из Стандартной библиотеки C++ используют механизм защиты от множественного включения. При просмотре содержимого заголовочного файла iostream можно заметить следующее:

ifndef _IOSTREAM_

define _IOSTREAM_

// основная часть кода

endif

Однако вернемся к примеру с библиотекой math.h, где мы попытаемся улучшить ситуацию с использованием защиты заголовков:

Библиотека math.h:

ifndef MATH_H

define MATH_H

int getSquareSides ( )

{

return 4 ;

}

endif

Файл geometry.h содержит информацию о геометрических операциях.

include "math.h"

Файл main.cpp:

include "math.h"

include "geometry.h"

int main ( )

{

return 0 ;

}

При включении заголовочного файла math.h в main.cpp, препроцессор обнаруживает, что MATH_H не определен, поэтому происходит определение MATH_H и содержимое math.h копируется в main.cpp. Затем main.cpp подключает заголовочный файл geometry.h, который в свою очередь также подключает math.h. Поскольку MATH_H уже был определен ранее, содержимое geometry.h не будет скопировано в main.cpp.

Вот как можно избежать повторения определений с использованием защиты заголовков.

#pragma once

Многие компиляторы поддерживают менее сложную, альтернативную версию header guards — pragma директиву:

pragma once

// основная часть кода

Директива pragma once применяется для защиты заголовочных файлов от множественного включения, но она обладает и другими преимуществами: она более компактная и менее подвержена ошибкам.

Тем не менее, pragma once не входит в стандарт языка C++ и не все компиляторы ее поддерживают (хотя большинство современных компиляторов это делают).

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

Тест

Пожалуйста, внесите защиту от множественного включения в следующий заголовочный файл:

Файл add.h содержит следующий код:

int add ( int x , int y ) ;

146   0  

Comments

    Ничего не найдено.