Перейти к содержимому

Как защитить хедер от повторного включения

  • автор:

Re: Re: задолбали разработчики, ниче не пишут че надо. сам вот взялся

[root@mobile100 cex]# c++ main.c f.c -o silly f.c:2:15: warning: extra tokens at end of #ifndef directive f.c:3:15: warning: ISO C requires whitespace after the macro name In file included from /usr/include/c++/3.2.2/backward/iostream.h:31, from f.c:4: /usr/include/c++/3.2.2/backward/backward_warning.h:32:2: warning: #warning This file includes at least one deprecated or antiquated header. Please consider using one of the 32 headers found in section 17.4.1.2 of the C++ standard. Examples include substituting the header for the header for C++ includes, or instead of the deprecated header . To disable this warning use -Wno-deprecated. [root@mobile100 cex]#

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

anonymous
( 17.11.03 20:18:43 MSK )

Re: Re: задолбали разработчики, ниче не пишут че надо. сам вот взялся

// f.c #ifndef header.h /*HEADER - это название твоего хидера*/ #define header.h #include #include "header.h" #endif void f () < cout [root@mobile100 cex]# c++ main.c f.c -o silly f.c:2:15: warning: extra tokens at end of #ifndef directive f.c:3:15: warning: ISO C requires whitespace after the macro name In file included from /usr/include/c++/3.2.2/backward/iostream.h:31, from f.c:4: /usr/include/c++/3.2.2/backward/backward_warning.h:32:2: warning: #warning This file includes at least one deprecated or antiquated header. Please consider using one of the 32 headers found in section 17.4.1.2 of the C++ standard. Examples include substituting the header for the header for C++ includes, or instead of the deprecated header . To disable this warning use -Wno-deprecated. [root@mobile100 cex]# вот так вот!! :( Может быть оно дает варнинг на то, что если я определю какюннить функцию, а она вдруг гдето зарезервированая в другой системной библиотеке? чето ниче не запостить. !!

vilfred ☆☆
( 17.11.03 20:19:51 MSK )

Re: Re: задолбали разработчики, ниче не пишут че надо. сам вот взялся

по этому поводу: // f.c #include #include "header.h" #endif void f () < cout дает ошибку [root@mobile100 cex]# c++ main.c f.c -o silly f.c:2:18: stream: No such file or directory f.c: In function `void f()': f.c:5: `cout' undeclared (first use this function) f.c:5: (Each undeclared identifier is reported only once for each function it appears in.) [root@mobile100 cex]# на это // f.c #include #include "header.h" void f () < cout дает ошибку File f.c not changed so no update needed. [root@mobile100 cex]# c++ main.c f.c -o silly f.c: In function `void f()': f.c:5: `cout' undeclared (first use this function) f.c:5: (Each undeclared identifier is reported only once for each function it appears in.) [root@mobile100 cex]# Че делать? Короче, в принципе оно конешно работает, но как то после #!/usr/bin/perl -w или use strict; use warnings непонятно, откуда это все, че ей не нравится. Может команда cout устарела просто со времен страустрапа? Причем, если я кимпилю gcc, выскакивает ошибка что нет либы. Если я компилю cc выскакивает что нет либы stream или iostream не помогало даже принудительно прописывание #include д

vilfred ☆☆
( 17.11.03 20:25:44 MSK )

Re: Re: Re: задолбали разработчики, ниче не пишут че надо. сам вот взялся

#include . std::cout 
abbr ★ 
( 17.11.03 20:37:08 MSK )

Re: Re: Re: Re: задолбали разработчики, ниче не пишут че надо. сам вот взялся

vilfred ☆☆
( 17.11.03 20:51:44 MSK )

Не того Страустроупа читал, ищи 3-ю редакцию

Читай не просто С++, а стандарт ISO С++. Там все стандартные заголовочные файлы заменены на такие же, но без раширений. И все помещено в namespace std.

anonymous
( 24.11.03 15:19:42 MSK ) Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.

Похожие темы

  • Форум ошибка при компеляции (2004)
  • Форум warning: #warning This file includes at least. (2005)
  • Форум Языками не владею (2004)
  • Форум Проблеммы компиляции в C++ (2004)
  • Форум Заголовочный файл (2006)
  • Форум [ Perl ] Проблема с использованием inline CPP (2008)
  • Форум проблема с с++ (2004)
  • Форум контейнеры (2005)
  • Форум не компилируются 5 строчек кода =( (2005)
  • Форум начинаюшему с++ помогите (2006)

Защита от повторного включения заголовка

В проекте на C, код которого я недавно стал смотреть, используется защита от повторного включения заголовка:

#ifndef H_HEADER #define H_HEADER // Header itself #endif 

Для унаследованного кода (где все и так работает), это нормально. Но для новых исходников хотелось бы что-нибудь проще. Есть ли аналог/заменитель в C?

Отслеживать
задан 14 дек 2010 в 14:15
34.5k 26 26 золотых знаков 98 98 серебряных знаков 214 214 бронзовых знаков

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

В C есть специальная директива

#pragma once 

которая указывает компилятору, что заголовок должен быть включен только один раз.

Отслеживать
ответ дан 17 дек 2010 в 10:46
34.5k 26 26 золотых знаков 98 98 серебряных знаков 214 214 бронзовых знаков

Насколько мне известно, #pragma once - это специфика Microsoft VC++, следовательно в Unix мире не может быть использована.

17 дек 2010 в 10:55

#pragma once не входит в стандарт C и не все компиляторы ее поддерживают; полностью портируемый код должен избегать ее. Тем не менее, GCC поддерживает эту директиву, поэтому и на Unix она доступна.

Как защитить хедер от повторного включения

Объясните достоинства обычной внутренней защиты #include

#if !defined(HEADER123_HPP) #define HEADER123_HPP //некий код #endif

Чем она лучше такой:

#if defined(HEADER123_HPP) #error wrong dependence detected #endif #define HEADER123_HPP //некий код

Re: защита от повторного include

От: jazzer Skype: enerjazzer
Дата: 26.02.06 18:42
Оценка:

Здравствуйте, Dmi_3, Вы писали:

D_>Объясните достоинства обычной внутренней защиты #include

D_>

D_>#if !defined(HEADER123_HPP) D_>#define HEADER123_HPP D_>//некий код D_>#endif D_>

D_>Чем она лучше такой:

D_>

D_>#if defined(HEADER123_HPP) D_>#error wrong dependence detected D_>#endif D_>#define HEADER123_HPP D_>//некий код D_>

Это защита не от того, о чем ты подумал.
Представь, что ты включил хедер a.h в b.h и в c.h, а потом включаешь b.h и c.h в d.h.

jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got If you always do what you always did

Re: защита от повторного include

От: zitz
Дата: 26.02.06 20:03
Оценка:

Здравствуйте, Dmi_3, Вы писали:

D_>Объясните достоинства обычной внутренней защиты #include

D_>

D_>#if !defined(HEADER123_HPP) D_>#define HEADER123_HPP D_>//некий код D_>#endif D_>

Читай по порядку: если КодЗадефайнен (т.е. мы его уже вставляли), то его пропустить, иначе вставить код и задефайнить.

D_>Чем она лучше такой:

D_>

D_>#if defined(HEADER123_HPP) D_>#error wrong dependence detected D_>#endif D_>#define HEADER123_HPP D_>//некий код D_>

Читай по порядку: если КодЗадезайнен вывести еггог и дальше вставить некий код, иначе задефайнить и вставить код.

Помоему разница налицо! В первом случае повторная вставка кода будет игнорироваться, а во втором высыпать куча ошибок.
Хотя если нужно чтобы так и было то второй вариант лучше, т.е. как написано в топике "защита от повторного include", хотя с какой целью писать хеадер чтобы его нельзя было использовать в нескольких частях проги непонятно.

Re[2]: защита от повторного include

От: jazzer Skype: enerjazzer
Дата: 26.02.06 20:21
Оценка:

Здравствуйте, zitz, Вы писали:

D_>>

D_>>#if defined(HEADER123_HPP) D_>>#error wrong dependence detected D_>>#endif D_>>#define HEADER123_HPP D_>>//некий код D_>>

Z>Читай по порядку: если КодЗадезайнен вывести еггог и дальше вставить некий код, иначе задефайнить и вставить код.
Z>Помоему разница налицо! В первом случае повторная вставка кода будет игнорироваться, а во втором высыпать куча ошибок.

Не так.
#error выдает ошибку и останавливает компиляцию.
Так что кучи ошибок не будет, будет всего одна (если у тебя, конечно, не параллельная сборка 🙂 ).

jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got If you always do what you always did

Re: защита от повторного include

От: Андрей Тарасевич
Дата: 26.02.06 20:52
Оценка:

Здравствуйте, Dmi_3, Вы писали:

D_>Объясните достоинства обычной внутренней защиты #include

D_>

D_>#if !defined(HEADER123_HPP) D_>#define HEADER123_HPP D_>//некий код D_>#endif D_>

D_>Чем она лучше такой:

D_>

D_>#if defined(HEADER123_HPP) D_>#error wrong dependence detected D_>#endif D_>#define HEADER123_HPP D_>//некий код D_>

Повторное включение заголовочного фала в одну и ту же единицу трансляции не является чем-то ненормальным. Поэтому ругаться на это через '#error' — это непонятная и странная идея.

Под "защитой от посторного включения" имеется я виду не запрещение физического повторного включения, а избежание повторной компиляции содержимого заголовочного фала в ситуации, когда он оказался включенным повторно в одну и ту же единицу трансляции.

Best regards,
Андрей Тарасевич
Re[2]: защита от повторного include

От: Andrej Kalinicenko
Дата: 27.02.06 10:14
Оценка:

" Андрей Тарасевич " <2174@users.rsdn.ru>schrieb im Newsbeitrag news:1700156@news.rsdn.ru.
> D_>

> D_>#if defined(HEADER123_HPP) > D_>#error wrong dependence detected > D_>#endif > D_>#define HEADER123_HPP > D_>//некий код > D_>

>
> Повторное включение заголовочного фала в одну и ту же единицу трансляции не является чем-то ненормальным. Поэтому ругаться на это через '#error' — это непонятная и странная идея.
>

Видел чудака который в таком заголовчном файле глобальные переменные описывал

Раздельная компиляция программ на C++

Когда мы пишем программу на C/C++ в одном файле, проблем обычно не возникает. Они ждут того момента, когда исходный текст необходимо разбить на несколько файлов. В этой статье я постараюсь рассказать, как это сделать правильно.

  • Термины
  • От исходного кода к исполняемому модулю
  • Разделение текста программы на модули
  • Интерфейс и реализация
    • Что может быть в заголовочном файле
    • Что может быть в файле реализации

    Термины

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

    Исходный код — программа, написанная на языке программирования, в текстовом формате. А также текстовый файл, содержащий исходный код.

    Компилятор — программа, выполняющая компиляцию (неожиданно! не правда ли?). На данный момент среди начинающих наиболее популярными компиляторами C/C++ являются GNU g++ (и его порты под различные ОС) и MS Visual Studio C++ различных версий. Подробнее см. в Википедии статьи: Компиляторы, Компиляторы C++.

    Компиляция — преобразование исходного кода в объектный модуль.

    Объектный модуль — двоичный файл, который содержит в себе особым образом подготовленный исполняемый код, который может быть объединён с другими объектными файлами при помощи редактора связей (компоновщика) для получения готового исполняемого модуля, либо библиотеки. (подробности)

    Компоновщик (редактор связей, линкер, сборщик) — это программа, которая производит компоновку («линковку», «сборку»): принимает на вход один или несколько объектных модулей и собирает по ним исполнимый модуль. (подробности)

    Исполняемый модуль (исполняемый файл) — файл, который может быть запущен на исполнение процессором под управлением операционной системы. (подробности)

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

    IDE (англ. Integrated Development Environment) — интегрированная среда разработки. Программа (или комплекс программ), предназначенных для упрощения написания исходного кода, отладки, управления проектом, установки параметров компилятора, линкера, отладчика. Важно не путать IDE и компилятор. Как правило, компилятор самодостаточен. В состав IDE компилятор может не входить. С другой стороны с некоторыми IDE могут быть использованы различные компиляторы. (подробности)

    Объявление — описание некой сущности: сигнатура функции, определение типа, описание внешней переменной, шаблон и т.п. Объявление уведомляет компилятор о её существовании и свойствах.

    Определение — реализация некой сущности: переменная, функция, метод класса и т.п. При обработке определения компилятор генерирует информацию для объектного модуля: исполняемый код, резервирование памяти под переменную и т.д.

    От исходного кода к исполняемому модулю

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

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

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

    Итак, допустим, у нас есть программа на C++ «Hello, World!»:

     int main()

    Сначала исходный код обрабатывается препроцессором. Препроцессор находит директиву #include , ищет файл iostream и заменяет директиву текстом из этого файла, попутно обрабатывая все директивы препроцессора во включаемом тексте.

    Файл, указанный в директиве #include , в данном случае является заголовочным файлом (или «хеадером», «хидером», «заголовком»). Это обычный текстовый файл, содержащий объявления (объявления типов, прототипы функций, шаблоны, директивы препроцессора и т.п.). После текстуального включения заголовочного файла в текст программы (или модуля) становится возможным использование в тексте программы всего того, что описано в этом заголовочном файле.

    Затем результат работы препроцессора передаётся компилятору. Компилятор производит весь положенный комплекс работ: от синтаксического разбора и поиска ошибок до создания объектного файла (понятно, что если имеются синтаксические ошибки, то объектный файл не создаётся). В объектном файле обычно имеется таблица внешних ссылок — некая таблица, в которой, в частности, перечислены имена подпрограмм, которые используются в объектном модуле, но код которых отсутствует в данном объектном модуле. Эти подпрограммы внешние по отношению к модулю.

    Исходный код, который может быть откомпилирован, называется единицей компиляции. Наша программа содержит одну единицу компиляции.

    Что бы получить нормальный исполняемый модуль, необходимо «разрешить» внешние ссылки. Т.е. добавить в исполняемый модуль код отсутствующих подпрограмм и настроить соответствующим образом все ссылки на этот код. Этим занимается компоновщик. Он анализирует таблицу внешних ссылок объектного модуля, ищет в объектных библиотеках недостающие модули, копирует их в исполняемый модуль и настраивает ссылки. После этого исполняемый модуль готов.

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

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

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

    Разделение текста программы на модули

    Разделение исходного текста программы на несколько файлов становится необходимым по многим причинам:

    1. С большим текстом просто неудобно работать.
    2. Разделение программы на отдельные модули, которые решают конкретные подзадачи.
    3. Разделение программы на отдельные модули, с целью повторного использования этих модулей в других программах.
    4. Разделение интерфейса и реализации.

    Я намеренно использовал слово «модуль», поскольку модулем может быть как класс, так и набор функций — вопрос используемой технологии программирования.

    Как только мы решаем разделить исходный текст программы на несколько файлов, возникают две проблемы:

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

    Первая проблема — чисто техническая. Она решается чтением руководств по компилятору и/или линкеру, утилите make или IDE. В самом худшем случае просто придётся проштудировать все эти руководства. Поэтому на решении этой проблемы мы останавливаться не будем.

    Вторая проблема — требует гораздо более творческого подхода. Хотя и здесь существуют определённые рекомендации, несоблюдение которых приводит либо к невозможности собрать проект, либо к трудностям в дальнейшем развитии проекта.

    Во-первых, нужно определить какие части программы выделить в отдельные модули. Что бы это получилось просто и естественно, программа должна быть правильно спроектирована. Как правильно спроектировать программу? — на эту тему написано много больших и правильных книг. Обязательно поищите и почитайте книги по методологии программирования — это очень полезно. А в качестве краткой рекомендации можно сказать: вся программа должна состоять из слабо связанных фрагментов. Тогда каждый такой фрагмент может быть естественным образом преобразован в отдельный модуль (единицу компиляции). Обратите внимание, что под «фрагментом» подразумевается не просто произвольный кусок кода, а функция, или группа логически связанных функций, или класс, или несколько тесно взаимодействующих классов.

    Во-вторых, нужно определить интерфейсы для модулей. Здесь есть вполне чёткие правила.

    Интерфейс и реализация

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

    Таким образом, модуль состоит из двух файлов: заголовочного (интерфейс) и файла реализации.

    Заголовочный файл, как правило, имеет расширение .h или .hpp, а файл реализации — .cpp для программ на C++ и .c, для программ на языке C. (Хотя в STL включаемые файлы вообще без расширений, но, по сути, они являются заголовочными файлами.)

    Заголовочный файл должен содержать все объявления, которые должны быть видны снаружи. Объявления, которые не должны быть видны снаружи, делаются в файле реализации.

    Что может быть в заголовочном файле

    Правило 1. Заголовочный файл может содержать только объявления. Заголовочный файл не должен содержать определения.

    То есть, при обработке содержимого заголовочного файла компилятор не должен генерировать информацию для объектного модуля.

    Единственным «исключением» из этого правила является определение метода в объявлении класса. Но по стандарту языка, если метод определён в объявлении класса, то для этого метода используется инлайновая подстановка. Поэтому, такое объявление не порождает исполняемого кода — код будет генерироваться компилятором только при вызове этого метода.

    Аналогичная ситуация и с объявлением переменных-членов класса: код будет порождаться при создании экземпляра этого класса.

    Правило 2. Заголовочный файл должен иметь механизм защиты от повторного включения.

    Защита от повторного включения реализуется директивами препроцессора:

    Для препроцессора при первом включении заголовочного файла это выглядит так: поскольку условие "символ SYMBOL не определён" ( #ifndef SYMBOL ) истинно, определить символ SYMBOL ( #define SYMBOL ) и обработать все строки до директивы #endif . При повторном включении — так: поскольку условие " символ SYMBOL не определён" ( #ifndef SYMBOL ) ложно (символ был определён при первом включении), то пропустить всё до директивы #endif .

    В качестве SYMBOL обычно применяют имя самого заголовочного файла в верхнем регистре, обрамлённое одинарными или сдвоенными подчерками. Например, для файла header.h традиционно используется #define __HEADER_H__ . Впрочем, символ может быть любым, но обязательно уникальным в рамках проекта.

    В качестве альтернативного способа может применяться директива #pragma once . Однако преимущество первого способа в том, что он работает на любых компиляторах.

    Заголовочный файл сам по себе не является единицей компиляции.

    Что может быть в файле реализации

    Файл реализации может содержать как определения, так и объявления. Объявления, сделанные в файле реализации, будут лексически локальны для этого файла. Т.е. будут действовать только для этой единицы компиляции.

    Правило 3. В файле реализации должна быть директива включения соответствующего заголовочного файла.

    Понятно, что объявления, которые видны снаружи модуля, должны быть также доступны и внутри.

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

    Правило 4. В файле реализации не должно быть объявлений, дублирующих объявления в соответствующем заголовочном файле.

    При выполнении Правила 3, нарушение Правила 4 приведёт к ошибкам компиляции.

    Практический пример

    Допустим, у нас имеется следующая программа:

    main.cpp

     using namespace std; const int cint = 10; // глобальная константа int global_var = 0; // глобальная переменная int module_var = 0; // глобальная переменная для func1 и func2 int func1() < ++global_var; return ++module_var; >int func2() < ++global_var; return --module_var; >class CClass < public: CClass() : priv(cint) < ++counter; >~CClass() < --counter; >void change(int arg); int get_priv() const; int get_counter() const; private: int priv; static int counter; >; int CClass::counter = 0; void CClass::change(int arg) < priv += arg; >int CClass::get_priv() const < return priv; >int CClass::get_counter() const < return counter; >int main()

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

    Итак, что у нас имеется?

    1. глобальная константа cint , которая используется и в классе, и в main ;
    2. глобальная переменная global_var , которая используется в функциях func1 , func2 и main ;
    3. глобальная переменная module_var , которая используется только в функциях func1 и func2 ;
    4. функции func1 и func2 ;
    5. класс CClass ;
    6. функция main .

    Вроде вырисовываются три единицы компиляции: (1) функция main , (2) класс CClass и (3) функции func1 и func2 с глобальной переменной module_var , которая используется только в них.

    Не совсем понятно, что делать с глобальной константой cint и глобальной переменной global_var . Первая тяготеет к классу CClass , вторая — к функциям func1 и func2 . Однако предположим, что планируется и эту константу, и эту переменную использовать ещё в каких-то, пока не написанных, модулях программы. Поэтому прибавится ещё одна единица компиляции.

    Теперь пробуем разделить программу на модули.

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

    globals.h

    globals.cpp

    Обратите внимание, что глобальная переменная в заголовочном файле имеет спецификатор extern . При этом получается объявление переменной, а не её определение. Такое описание означает, что где-то существует переменная с таким именем и указанным типом. А определение этой переменной (с инициализацией) помещено в файл реализации. Константа описана в заголовочном файле.

    С объявлением констант в заголовочном файле существует одна тонкость. Если константа тривиального типа, то её можно объявить в заголовочном файле. В противном случае она должна быть определена в файле реализации, а в заголовочном файле должно быть её объявление (аналогично, как для переменной). «Тривиальность» типа зависит от стандарта (см. описание того стандарта, который используется для написания программы).

    Также обратите внимание (1) на защиту от повторного включения заголовочного файла и (2) на включение заголовочного файла в файле реализации.

    Затем выносим в отдельный модуль функции func1 и func2 с глобальной переменной module_var . Получаем ещё два файла:

    funcs.h

    funcs.cpp

     int func2()

    Поскольку переменная module_var используется только этими двумя функциями, её объявление в заголовочном файле отсутствует. Из этого модуля «на экспорт» идут только две функции.

    В функциях используется переменная из другого модуля, поэтому необходимо добавить #include "globals.h" .

    Наконец выносим в отдельный модуль класс CClass :

    CClass.h

    ; #endif // __CCLASS_H__ 

    CClass.cpp

     CClass::~CClass() < --counter; >void CClass::change(int arg) < priv += arg; >int CClass::get_priv() const < return priv; >int CClass::get_counter() const

    Обратите внимание на следующие моменты.

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

    (2) Класс имеет статический член класса. Т.е. для всех экземпляров класса эта переменная будет общей. Её инициализация выполняется не в конструкторе, а в глобальной области модуля.

    (3) В файл реализации добавлена директива #include "globals.h" для доступа к константе cint .

    Классы практически всегда выделяются в отдельные единицы компиляции.

    В файле main.cpp оставляем только функцию main . И добавляем необходимые директивы включения заголовочных файлов.

    main.cpp

     #include "funcs.h" #include "CClass.h" #include "globals.h" using namespace std; int main()

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

    Типичные ошибки

    Ошибка 1. Определение в заголовочном файле.

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

    Ошибка 2. Отсутствие защиты от повторного включения заголовочного файла.

    Тоже проявляет себя при определённых обстоятельствах. Может вызывать ошибку компиляции «многократное определение символа . ».

    Ошибка 3. Несовпадение объявления в заголовочном файле и определения в файле реализации.

    Обычно возникает в процессе редактирования исходного кода, когда в файл реализации вносятся изменения, а про заголовочный файл забывают.

    Ошибка 4. Отсутствие необходимой директивы #include .

    Если необходимый заголовочный файл не включён, то все сущности, которые в нём объявлены, останутся неизвестными компилятору. Вызывает ошибку компиляции «не определён символ . ».

    Ошибка 5. Отсутствие необходимого модуля в проекте построения программы.

    Вызывает ошибку компоновки «не определён символ . ». Обратите внимание, что имя символа в сообщении компоновщика почти всегда отличается от того, которое определено в программе: оно дополнено другими буквами, цифрами или знаками.

    Ошибка 6. Зависимость от порядка включения заголовочных файлов.

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

    Заключение

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Luxury Car Rental in Miami | Reserve a Premium Vehicle with RealCar