[БЕЗ_ЗВУКА] В предыдущем видео мы с вами улучшили наш проект, состоящий из нескольких файлов. Мы смогли сделать так, что наши заголовочные файлы стали независимыми. Мы теперь можем подключать их в произвольном порядке, и наш проект успешно компилируется. И сделали мы это за счет того, что сами заголовочные файлы стали подключать все необходимые им зависимости. Но мы сделали это только для двух файлов. У нас остался файл tests.h, в котором мы пока подключили только тестовый фреймворк. Но это не все зависимости, которые нужны этому файлу. Например, файл с тестами вызывает функцию AddSynonyms, он, собственно, ее тестирует. И эта функция у нас определена в synonyms.h, и этот файл synonyms.h не подключается в tests.h. И соответственно, даже если мы пойдем и tests.h поставим первым, то у нас проект не скомпилируется как раз таки по причине того, что tests.h не знает, что такое AddSynonyms. Поэтому мы должны закончить начатое дело и добавить в tests.h все зависимости, в частности нам нужно добавить synonyms.h. Давайте теперь скомпилируем нашу программу. И что пошло не так? Она не компилируется. Причем она не компилируется с каким-то странным сообщением. Она пишет redefinition of и дальше указывает функции наши — AreSynonyms, GetSynonymCount и AddSynonyms. И непонятно, в чем дело, потому что он указывает на эти функции, тут есть еще снова какие-то указания. В общем, что-то идет не так, и нам нужно разобраться, что такое redefinition of, что значит переопределение. И давайте мы, для того чтобы разобраться, что это такое, снова переключимся в наш маленький проект. У нас есть наш маленький проект. состоящий всего из двух файлов: sum.h и маленькая функция main, которая вызывает функцию Sum. И мы сделаем вот такую вещь: мы сделаем #include "sum.h" дважды. Как бы, почему мы не можем этого сделать? Мы можем в реальной практике сделать это случайно. Скомпилировали нашу программу, и мы видим ту же самую ошибку компиляции: redefinition of функция intSum. Это та же самая проблема, с которой мы столкнулись в большом проекте. И давайте разбираться, что происходит. Давайте переключимся в консоль операционной системы и снова выполним препроцессинг этого нашего кода. Вызовем g++ −E how_include_works.cpp. И мы выполним препроцессинг, и что мы видим? Видим мы, что в результате препроцессинга у нас получилось в файле две функции Sum. Вот она первая функция Sum, а вот она вторая. И вот этот файл, который мы сейчас видим на экране, с двумя функциями Sum, он подается на вход компилятору, который его должен скомпилировать. Компилятор видит: мне дали две одинаковые функции. И он об этом нам сообщает. Он нам сообщает с помощью ошибки компиляции redefinition — повторное определение. Давайте теперь снова закроем наш маленький проект и вернемся в большой проект. И посмотрим мы сейчас на файл tests.h и файл coursera.cpp. Вот смотрите, у нас есть tests.h, в файле coursera.cpp у нас включается tests.h. При этом он подключает synonyms.h. А synonyms.h также включается в файле coursera.cpp, то есть происходит вот такая вещь. Вот наш файл с нашим решением. Он включает в себя synonyms.h. Файл tests.h также включает в себя synonyms.h. И за счет включения tests.h в coursera.cpp вот этот вот файл synonyms.h дважды включается в coursera.cpp. У нас возникает двойное включение этого заголовочного файла в наш файл с решением задачи. У нас все функции, которые в нем объявлены, попадают в файл дважды, и мы получаем ту же самую ошибку redefinition, потому что каждая из двух функций оказывается объявленной дважды. Давайте теперь подумаем и обсудим, как эту проблему можно решить, как можно избежать двойного включения. Делается это очень просто. Мы просто в начало каждого заголовочного файла вставляем специальную директиву для процессора #pragma once. Давайте применим ее для начала к нашему большому проекту. Давайте пойдем в synonyms.h для начала, потому что он у нас включается дважды, и прямо в самом начале напишем #pragma once. Скомпилируем нашу программу. И мы видим, что она скомпилировалась. То есть добавление вот этой вот этой директивы, сказало компилятору, что, дорогой компилятор, пожалуйста, учитывай количество раз, когда этот заголовочный файл был включен, и все повторные включения, пожалуйста, выбрасывай. И вставка вот этой вот директивы в начало каждого заголовочного файла и защищает нас от того, чтобы у нас возникала проблема двойного включения. Поэтому давайте возьмем все наши заголовочные файлы и добавим в них #pragma once. И убедимся, что наша программа успешно компилируется. Давайте снова, чтобы посмотреть, как это работает, снова вернемся в маленький проект, [БЕЗ_ЗВУКА] и у нас здесь осталось двойное включение. И как вы помните, это двойное включение приводило к двум определениям функции Sum в файле, который получался после препроцессинга. Давайте возьмем sum.h и здесь тоже напишем #pragma once. Скомпилируем программу. Она компилируется. И давайте снова выполним препроцессинг нашего файла. Вот файл после препроцессинга. Что мы видим? Мы видим, что функция Sum в файле, который получился после препроцессинга, она у нас есть только один раз. То есть мы явно видим, что директива #pragma once действительно оказывает влияние на препроцессор и обеспечивает нам однократное включение заголовочного файла, даже несмотря на то, что мы явно включили его два раза. Здесь, я думаю, у вас мог возникнуть вопрос: а почему в C++ по умолчанию компилятор не отслеживает, если точнее говорить, то препроцессор, не отслеживает что заголовочные файлы включаются несколько раз, и почему препроцессор по умолчанию не выкидывает повторные включения? И ответ здесь дать очень сложно. Наверняка это какие-то исторические причины, потому что C++ делался обратно совместимым с C, и вообще C++ развивается так, чтобы не терять обратную совместимость. И видимо, по каким-то историческим причинам было сделано так, что нам нужно явно в каждом файле обязательно указывать вот эту вот директиву, чтобы избежать проблемы двойного включения. Потому что на практике на самом деле дважды включать один и тот же заголовочный файл бывает нужно просто крайне редко. Я не могу вспомнить в своей практике ни одного случая, когда мне это было действительно нужно. Но вот, к сожалению, это так, мы действительно должны каждый заголовочный файл предварять вот такой директивой. Но вы, естественно, можете подумать о том, что ладно, хорошо, должны, но ведь это неудобно, потому что мы должны помнить, что каждый раз, когда мы сделали заголовочный файл, мы должны не забыть в его начало добавить эту директиву, а когда пишешь код, у тебя летит мысль, можно забыть о такой вот достаточно технической вещи. Но здесь нам на помощь приходит IDE. И большинство современных IDE делает это автоматически. Давайте посмотрим, как это делать в Eclipse. Все предельно просто. Идем в Window, Preferences, здесь нас интересует пункт меню C/C++. Здесь мы открываем Code Style и Code Templates. Распахиваем пункт Files, и здесь есть C++ Header File, Default C++ header template. Нажимаем Edit, и это окно, в которое мы можем ввести шаблон, который будет вставляться во все заголовочные файлы, которые мы создаем. Сюда можно добавлять специальные макропеременные, которые вставляют ваше имя, дату создания файла, имя проекта и так далее и так далее. Но вот мы сюда прямо и напишем #pragma once, перевод строки. Нажмем OK, Apply, Apply and Close, чтобы точно все сохранилось и применилось, и давайте проверим, что у нас получилось, получилось ли у нас это. Вот у нас сейчас открыт наш маленький проект, давайте добавим в него заголовочный файл. Add New, Header File, header.h. Нажимаем Finish, и мы видим, что у нас создался заголовочный файл header.h, назван он так, как мы и попросили. И он по умолчанию сразу идет с проставленной директивой #pragma once. Таким образом, мы смогли настроить Eclipse так, чтобы он всегда вставлял эту директиву в начало, и мы никогда об этом не забудем и никогда не получим проблему двойного включения. Давайте подведем итоги. Мы с вами узнали, что в C++ если ничего не делать, то один и тот же заголовочный файл может быть дважды включен в программу. Из-за этого возникает проблема двойного включения, которую можно распознать по ошибке компиляции redefinition of и имя функции. Чтобы избежать проблемы двойного включения, нужно в начало каждого заголовочного файла добавлять директиву #pragma once. И мы не обязаны делать это руками, большинство современных IDE умеют делать это автоматически.