相对路径(如"../include/header.h")作为头文件的路径有哪些好处?

76

我已经查阅了来自stackoverflow的问题如何正确使用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'指令来查找头文件,但失去灵活性以及在子目录中编译的复杂性等似乎超过了好处。

那么,我是不是忽视了一些好处?


谢谢大家的意见。我认为共识是我没有忽视使用 ".." 符号的任何主要好处。一般来说,我喜欢“somewhere/header.h”的符号;我在新项目中使用它。我正在处理的这个项目远非新项目。其中一个问题是有各种各样的头文件集,通常带有前缀,例如rspqr.hrsabc.hrsdef.hrsxyz.h。所有这些都与rsmp目录中的代码相关,但有些头文件在rsmp中,而其他头文件则在中央包含目录中,该目录没有像rsmp这样的子目录。(对于代码的各个其他区域也是如此;有多个位置的头文件,由其他的代码随机需要。)移动所有内容是一个主要问题,因为多年来代码已变得如此复杂。而且makefile在提供-I选项方面并不一致。总之,这是一个长达数十年的不那么良性的忽视的悲伤故事。修复它所有的问题而不破坏任何东西将是一项漫长而乏味的工作。


你似乎已经了解所有相关因素,所以这并不是一个问题。 - shoosh
1
我正在检查是否有我忽略的好处(因为我讨厌这种符号,但在工作中不得不使用它)。如果没有 "..",我对这种符号就没那么困扰了。 - Jonathan Leffler
使用相对路径会在使用单独的构建目录时导致巨大的头痛。例如,cd yourproject; mkdir build; cd build; ../configure && make - 如果您将继续维护代码,则修复它可能值得花费时间。 - Tim Post
相信我,tinkertim,我正在努力解决这个问题。然而,我没有统一进行更改的权限;这需要时间来完成(数年时间——代码库的守护者非常保守)。 - Jonathan Leffler
8个回答

53

我更喜欢使用路径语法,因为它非常清楚地表明了头文件所属的命名空间或模块。

#include "Physics/Solver.h"

非常自我描述,无需要求每个模块在头文件前缀上命名。

尽管我几乎从不使用 ".." 语法,但是我会让我的项目包含指定正确的基础位置。


32

#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,则会破坏 ./lib/feature/feature.c

我认为以下内容更可取:

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

我不会添加除了-I.之外的任何include路径,然后library.cfeature.c都会使用#include "projectname/include/header.h"。假设"projectname"很可能是唯一的,在大多数情况下,这不应该导致名称冲突或歧义。如果绝对必要(例如容纳特定于平台的自动生成的代码),您还可以使用include路径和/或make的VPATH功能将项目的物理布局分成多个目录(在使用#include "../../somefile.h"时,这会导致问题严重)。


2
是的,我在上一个项目中使用了“projectname/include/header.h”的方式(但是是从include/开始的)。我总是只放一个指向include/路径的-I,然后根据那个来包含文件。但我还没有深入分析“..”的问题。你说得很有道理。 - Johannes Schaub - litb
1
这似乎描述了头文件路径普遍脆弱的方式,而不是特定的相对路径。可以很容易地想出等效的问题场景,这些场景不涉及 .. - John McFarlane
我不会添加除了-I.之外的任何包含路径条目...更常见的做法是添加“include”目录,即-Iinclude。这样你的问题就解决了,在适当的情况下可以使用..。以这种方式在您的包含路径中添加libinclude似乎是一种代码异味。 - John McFarlane

10

如果:

  • 你想包含一个与当前文件固定位置的文件,
  • 你正在使用POSIX系统或VC++进行构建,
  • 你希望避免不确定会被包含哪个文件。

那么在你的#include ""指令路径开头加上一个或多个"../"是一种好方式。

即使你的项目没有错误,也很容易用绝对路径来指定相互关联的文件,从而被第三方滥用。因此,提供一个包含错误并导致难以诊断故障的例子是非常容易的。

例如,考虑以下项目布局:

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

如何包含your_lib/include/bar/header2.h这个文件在your_lib/include/foo/header1.h的头文件中?考虑两种选择:

  1. #include <bar/header2.h>

    假设your_lib/includetheir_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,在那里找到正确的文件。不会使用头文件搜索路径,几乎没有歧义。

出于给定理由,我强烈建议在这种情况下选择第二种方法。

回应其他答案中的一些争论:

  • @andrew-grant :

    ...它清楚地表明头文件属于哪个命名空间或模块。

    也许。但是相对路径可以解释为“在这个同一个模块中”。当存在多个位于不同模块的具有相同名称的目录时,它们提供了清晰度。

  • @bk1e :

    ...它经常是偶然的...

    我认为相对路径只有在项目从一开始就出问题并且很容易修复的极少数情况下才会工作。在不导致编译器错误的情况下遇到这种名称冲突似乎是不太可能的。常见的场景是依赖项目的文件包含您的其中一个头文件,该头文件又包含您的另一个头文件。将测试套件编译在与该依赖项目隔离的环境中应该会导致“找不到文件或目录”的错误。

  • @singlenegationelimination

    ...这不是可移植的,标准不支持。

    ISO C标准可能不指定程序编译或运行的所有系统的详细信息。这并不意味着它们不受支持,只是标准没有过度规定C将在哪些平台上运行。(对于常见现代系统中""<>的解释方式的区别可能起源于POSIX标准。)

  • @daniel-paull

    “..”假设相对位置并且很脆弱

    易碎的怎么样?大概会受到两个文件的位置关系的影响。因此,“..”应仅(并


第三个选项#include "bar/header2.h"怎么样?虽然它与选项1并没有太大的区别,所以你可能已经将其归入了选项1。 - Jonathan Leffler
是的,这与选项1相同,只是编译器首先会尝试相对于当前路径打开bar/header2.h,即your_lib/include/foo/。换句话说,它将寻找your_lib/include/foo/bar/header2.h,但在此情况下不是有效路径。 - John McFarlane
2
谢谢你的回答,约翰。我很失望之前的回答忽略了你提出的重要观点。我确实赞同指定包含相对于模块名称的方式有助于清晰度,但我真的不喜欢这给项目使用特定的“包含目录”带来的要求。当一个模块使用源相关的包含路径时,消费者不需要做任何假设关于代码存在的位置,并且项目也没有添加模块特定包含路径的要求。 - Tom

10

我不是专业人士,但我认为你不应该在实际的C或C ++源文件中放置..,因为这不是可移植的,标准不支持它。这就像在Windows上使用\一样。只有在你的编译器无法使用其他方法时才能这样做。


3
好观点。标准似乎甚至不能保证您可以使用斜杠。"实现为由一个或多个非数字 (lex.name) 序列后跟句点(.)和单个非数字提供唯一映射。" 它将非数字定义为基本上是 [_a-zA-Z] 或 \uNNNN。 - bk1e
3
这是真的 - 但所涉及的软件已经在多种平台上编译了许多年(超过20年),因此这更多是一种理论上的而非实际上的责任。 - Jonathan Leffler
12
IANALL没有任何谷歌搜索结果。我猜它的意思是“我不是语言律师”? - Martijn Courteaux
在神经和词汇术语中? - Laurie Stearn
1
@singlenegationelimination 请提供一些证据表明标准不支持 .. - John McFarlane

2
将您的源代码树视为嵌套名称空间,而包含路径则允许您将目录拉入此命名空间的根部。然后问题就变成了为您的代码库形成逻辑名称空间,而不考虑代码在磁盘上的组织方式。
我会避免使用如下路径:
- "include/foo/bar.h"——“include”似乎是不合逻辑和多余的。 - "../foo/bar.h"——“..”假定了相对位置并且很容易出错。 - "bar.h"——除非bar.h在当前目录中,否则这会污染全局名称空间并导致模糊不清的情况。
个人而言,我倾向于将像以下这样的路径添加到我的项目包含路径中:"..;../..;../../..;../../../.."
这使您可以对您的 #include 进行一种隐藏规则,并允许一些自由地移动标头文件,而不破坏其他代码。当然,这是以引入绑定到错误标头文件的风险为代价的,如果您不小心,因为非完全限定名称可能是(或随时间变得)模糊的。
我倾向于在公共头文件中完全限定 #include,因此任何使用我的代码的第三方都不需要将 "..;../..;../../..;../../../.." 添加到其项目中——这仅仅是为我的私有代码和构建系统提供方便。

1
你的路径中的 ; 的目的是什么? - Royi
1
";" 是 Windows 中的路径分隔符,具体来说,它是用于向 Visual Studio 提供一组要搜索的目录的方式。 - Daniel Paull
你能详细说明一下吗?我不确定我理解了。谢谢。 - Royi
这意味着首先在“..”中搜索,然后在“../..”中搜索,然后在“../../..”中搜索,以此类推。 - Daniel Paull
如果你刚才写的东西还不足以成为充分的理由,那我不知道你还需要什么。 - Daniel Paull
显示剩余9条评论

2

在Windows上使用相对路径时的另一个问题是MAX_PATH。当您的路径长度超过260时,例如在为Android进行交叉编译时,这会引发编译问题。


3
从 Windows 10 版本 1607 开始,常见的 Win32 文件和目录函数已经取消了 MAX_PATH 的限制。然而,您必须选择新行为以进行操作。 - phuclv

1
因为这样你可以将文件相对于项目根目录放置,当你将其检入源代码控制后,另一个开发人员在本地系统的不同位置检出时仍然可以正常工作。

4
我认为这是反对使用绝对路径的论点,但我并不是在询问那些(在我看来它们毫无疑问是不好的)。我并不确定这是否支持或反对任何相对路径名称的变体。 - Jonathan Leffler

0
扔出另一个绝对路径(或“扩展相对路径”)的参数,例如#include "./lib/subdir/~/subsubsubsubdir/header.h"... 当处理一个非常大的项目时,可能需要在链接行上使用许多"-I./~"选项。在我的当前项目中,这些选项占用的字符数近40k,这会导致由于命令行限制而出现错误。

虽然通常我不喜欢那种包含路径的明显不灵活性,但它可以帮助避免这种问题。例如,静态库可以通过"#include "lib_api.h"提供其API,并且所有其他组织都可以相对于该位置并且对用户不可见。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接