Каковы преимущества относительного пути, такого как "../include/header.h", для заголовка?

Я рассмотрел вопросы Как использовать директиву включение правильно и C++ #include семантики и ни адресов это - и не другие предложенного поэтому , когда я напечатал название ...

Каковы преимущества письма, если таковые имеются:

#include "../include/someheader.h"
#include "../otherdir/another.h"

по сравнению с использованием простого имени файла:

#include "someheader.h"
#include "another.h"

или, возможно, относительное имя без " .. ":

#include "include/someheader.h"
#include "otherdir/another.h"

Я вижу следующие проблемы:

  • Вы не можете переместить заголовок, не беспокоясь о том, какие исходные файлы включают его.
  • Вы можете получить очень длинные пути для заголовков в зависимостях и отчетах об ошибках. У меня сегодня был один с " ../dir1/include/../../include/../dir2/../include/header.h".

Единственное достоинство, которое я вижу, заключается в том, что, хотя вам не нужно перемещать файлы, вы можете уйти, не всегда используя -I директивы для поиска заголовков, но потеря гибкости и сложность компиляции в подподпрограммах -directories и т. д., похоже, перевешивают пользу.

Итак, я упускаю из виду выгоду?


Спасибо за вклад. Я думаю, что консенсус состоит в том, что в нотации с использованием «..» нет каких-либо серьезных преимуществ, которые я упускал из виду. В общем, мне нравится нотация «где-то / header.h»; Я использую его в новых проектах. Тот, над которым я работаю, совсем не новый.

Одна из проблем состоит в том, что существуют различные наборы заголовков, часто с приставкой , таких как rspqr.h, rsabc.h, rsdef.h, rsxyz.h . Все они связаны с кодом в rsmp каталоге, но некоторые из заголовков находятся внутри, rsmp а другие находятся в центральном каталоге include, в котором нет подкаталогов, таких как rsmp в нем. (И повторите для различных других областей кода; есть заголовки в нескольких местах, которые случайным образом нужны другим битам кода.) Перемещение чего-либо - серьезная проблема, потому что код с годами стал настолько запутанным. И в make-файлах не согласовано, какие -I опции предоставляются. В общем, это печальная история не столь безобидного пренебрежения на протяжении десятилетий. Исправить все, ничего не сломав, будет долгой и утомительной работой.

Ответов (8)

Решение

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

#include "Physics/Solver.h"

Самоописание не требует от каждого модуля добавления префикса к имени в заголовочных файлах.

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

Еще одна проблема с окнами с относительными путями - MAX_PATH. Это вызовет проблемы с компиляцией, например, если при кросс-компиляции для Android длина вашего пути превышает 260.

Бросив еще один аргумент в пользу абсолютного (или «расширенных относительно») включает в себя пути , как #include "./lib/subdir/~/subsubsubsubdir/header.h" ... При работе над действительно большим проектом один может потребовать много из "-I./~" вариантов на линии связи. В текущем моем проекте количество символов, занимаемых этими параметрами, составляет почти 40 КБ, что вызывает ошибки из-за ограничений командной строки.

Хотя обычно мне не нравится очевидная негибкость этого стиля пути включения, это может помочь избежать такого рода проблем. Например, статическая библиотека может предоставлять свой API, "#include "lib_api.h" а вся остальная организация может относиться к этому местоположению и быть невидимой для пользователя.

Начните путь ваших #include "" директив с последовательности из одного или нескольких ../ символов, когда:

  • вы хотите включить файл, чье совместное размещение с включающим файлом исправлено и
  • вы создаете систему POSIX или VC++ и
  • вы хотите избежать двусмысленности в том, какой файл будет включен.

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

Например, рассмотрим следующий макет проекта:

./your_lib/include/foo/header1.h
./your_lib/include/bar/header2.h
./their_lib/include/bar/header2.h

Как your_lib / include / foo / header1.h должен включать your_lib / include / bar / header2.h ? Рассмотрим два варианта:

  1. #include <bar/header2.h>

    Предполагая, что как your_lib / include, так и their_lib / include указаны в качестве путей поиска заголовков (например, с использованием GCC -Iили -isystemопций), тогда выбор того, какой будет выбран header2.h , зависит от порядка, в котором эти два пути ищутся.

  2. #include "../bar/header2.h"

    Первое местоположение, в котором компилятор будет искать, - это местоположение your_lib / include / foo / header1.h , то есть your_lib / include / foo / . Сначала он попробует your_lib / include / foo /../ bar / header2.h, который сократится до your_lib / include / bar / header2.h, где найдет правильный файл. Пути поиска заголовков не будут использоваться вообще, и здесь мало места для двусмысленности.

Я настоятельно рекомендую вариант 2) в этом случае по указанным причинам.

В ответ на некоторые аргументы в других ответах:

  • @ Эндрю-Грант говорит :

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

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

  • @ bk1e говорит :

    ... часто срабатывает случайно ...

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

  • @singlenegationelimination говорит

    ... это не переносимо, и стандарт не поддерживает его.

    Стандарт ISO C может не определять все детали систем, в которых программа компилируется или запускается. Это не означает, что они не поддерживаются, просто стандарт не определяет платформы, на которых будет работать C. (Различие между тем, как ""и <>интерпретируются в общих современных системах, вероятно, происходит из стандарта POSIX.)

  • @ Дэниел-Пол говорит

    ".." предполагает относительное расположение и хрупкий

    Как хрупкий? Предположительно чувствительно к совместному размещению двух файлов. Таким образом, " .." следует (и всегда) использовать только тогда, когда автор включающего файла контролирует их местоположение.

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

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

Я бы избегал таких путей:

  • "include/foo/bar.h" - "включить" кажется нелогичным и излишним
  • "../foo/bar.h" - ".." предполагает относительное расположение и хрупкий
  • "bar.h" - если bar.h не находится в текущем каталоге, это загрязняет глобальное пространство имен и вызывает двусмысленность.

Лично я обычно добавляю в свои проекты путь, подобный следующему, включая путь - "..;../..;../../..;../../../.." .

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

Я стараюсь полностью указывать #include s в общедоступных заголовках, поэтому третьим сторонам, использующим мой код, не нужно добавлять его "..;../..;../../..;../../../.." в свой проект - это просто удобство для моего частного кода и системы сборки.

Проблема в #include "../include/header.h" том, что он часто срабатывает случайно, а затем, казалось бы, несвязанное изменение заставит его перестать работать позже.

Например, рассмотрим следующий исходный макет:

./include/header.h
./lib/library.c
./lib/feature/feature.c

Допустим, вы запускаете компилятор с включенным путем -I. -I./lib . Что происходит?

  • ./lib/library.cможет сделать #include "../include/header.h", что имеет смысл.
  • ./lib/feature/feature.cтоже можно #include "../include/header.h", хотя в этом нет смысла. Это связано с тем, что компилятор пробует использовать #includeдирективу относительно местоположения текущего файла, и если это не удается, он пробует #includeдирективу относительно каждой -Iзаписи в #includeпути.

Более того, если вы позже удаляете -I./lib с #include пути, вы сломаетесь ./lib/feature/feature.c .

Я считаю что-то вроде следующего предпочтительнее:

./projectname/include/header.h
./projectname/lib/library.c
./projectname/lib/feature/feature.c

Я бы не стал добавлять какие-либо записи пути включения, кроме -I., а затем оба library.c и feature.c использовали бы #include "projectname/include/header.h" . Предполагая, что «имя проекта», вероятно, будет уникальным, в большинстве случаев это не должно приводить к конфликтам имен или двусмысленностям. Вы также можете использовать путь включения и / или VPATH функцию make для разделения физического макета проекта по нескольким каталогам, если это абсолютно необходимо (например, для размещения автоматически сгенерированного кода для конкретной платформы; это то, что действительно ломается при использовании #include "../../somefile.h" ).

IANALL, но я не думаю, что вам следует помещать .. их в реальные исходные файлы C или C++, потому что это непереносимо, и стандарт не поддерживает его. Это похоже на использование \ в Windows. Делайте это только в том случае, если ваш компилятор не может работать ни с каким другим методом.