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

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

Каковы преимущества написания:

#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 для поиска заголовков, но потерю гибкости и сложности. компиляция в подкаталогах и т. д., кажется, перевешивает выгоду.

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


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

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

Ответы [ 7 ]

36 голосов
/ 28 февраля 2009

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

#include "Physics/Solver.h"

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

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

24 голосов
/ 28 февраля 2009

Проблема с #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". Предполагая, что «имя проекта» может быть уникальным, это не должно приводить к конфликтам имен или неоднозначности в большинстве случаев. Вы также можете использовать функцию include path и / или make VPATH, чтобы разделить физический макет проекта на несколько каталогов, если это абсолютно необходимо (например, для поддержки автоматически сгенерированного кода для конкретной платформы; это то, что действительно ломается, когда вы используйте #include "../../somefile.h").

9 голосов
/ 28 февраля 2009

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

2 голосов
/ 28 февраля 2009

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

Я бы избегал путей вроде:

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

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

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

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

1 голос
/ 03 сентября 2018

Начинайте путь ваших директив #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 include your_lib / include / bar / header2.h ? Давайте рассмотрим два варианта:

  1. #include <bar/header2.h>

    Если предположить, что оба your_lib / include и its_lib / include указаны в качестве путей поиска заголовка (например, с использованием параметров -I или -isystem в GCC), тогда выбор которых 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) в этом случае по указанным причинам.

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

  • @ andrew-grant говорит :

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

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

  • @ bk1e говорит :

    ... часто будет работать случайно ...

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

  • @ singlenegationelidual говорит

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

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

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

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

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

1 голос
/ 13 октября 2017

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

1 голос
/ 28 февраля 2009

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

...