What are the benefits of a relative path such as "../include/header.h" for a header?
I've reviewed questions How to use include directive correctly and C++ #include semantics and neither addresses this - nor do the others suggested by SO when I typed the title...
What, if any, are the benefits of writing:
#include "../include/someheader.h"
#include "../otherdir/another.h"
compared with using just a plain file name:
#include "someheader.h"
#include "another.h"
or perhaps a relative name without the '..
':
#include "include/someheader.h"
#include "otherdir/another.h"
The problems I see are:
- You can't move a header without worrying about which source files include it.
- You can end up with very long paths for headers in dependencies and error reports. I had one today with "
../dir1/include/../../include/../dir2/../include/header.h
".
The only merit I can see is that while you do not need to move files around, you might be able to get away without always using '-I
' directives to find headers, but the loss of flexibility, and the complexity of compiling in sub-sub-directories, etc seems to outweigh the benefit.
So, am I overlooking a benefit?
Thanks for the inputs. I think the consensus is that there aren't any major benefits to the notation using ".." that I'm overlooking. In general terms, I like the "somewhere/header.h" notation; I do use it in new projects. The one I'm working on is anything but new.
One of the problems is that there are various sets of headers, often with a prefix such as rspqr.h
, rsabc.h
, rsdef.h
, rsxyz.h
. These are all related to code in the rsmp
directory, but some of the headers are in rsmp
and others are in the central include directory, which does not have sub-directories such as rsmp
in it. (And repeat for the various other areas of the code; there are headers in multiple locations, needed randomly by other bits of code.) Moving stuff around is a major problem because the code has gotten so convoluted over the years. And the makefiles are not consistent in which -I
options are provided. All in all, it is a sad story of not-so-benign neglect over a period of decades. Fixing it all without breaking anything is going to be a long, tedious job.
Solution 1:
I prefer the path syntax as it makes it very clear what namespace or module the header file belongs to.
#include "Physics/Solver.h"
Is very self-describing without requiring every module to prefix their name to header files.
I almost never use the ".." syntax though, instead I have my project includes specify the correct base locations.
Solution 2:
The problem with #include "../include/header.h"
is that it will often work by accident, and then a seemingly unrelated change will make it stop working later.
For example, consider the following source layout:
./include/header.h
./lib/library.c
./lib/feature/feature.c
And let's say that you're running the compiler with an include path of -I. -I./lib
. What happens?
-
./lib/library.c
can do#include "../include/header.h"
, which makes sense. -
./lib/feature/feature.c
can also do#include "../include/header.h"
, even though it doesn't make sense. This is because the compiler will try the#include
directive relative to the location of the current file, and if that fails, it will try the#include
directive relative to each-I
entry in the#include
path.
Furthermore, if you later remove -I./lib
from the #include
path, then you break ./lib/feature/feature.c
.
I find something like the following to be preferable:
./projectname/include/header.h
./projectname/lib/library.c
./projectname/lib/feature/feature.c
I wouldn't add any include path entries other than -I.
, and then both library.c
and feature.c
would use #include "projectname/include/header.h"
. Assuming that "projectname" is likely to be unique, this should not result in name collisions or ambiguities in most circumstances. You can also use the include path and/or make's VPATH
feature to split the project's physical layout across multiple directories if absolutely necessary (to accommodate platform-specific auto-generated code, for instance; this is something that really breaks down when you use #include "../../somefile.h"
).
Solution 3:
Begin the path of your #include ""
directives with a sequence of one or more "../
"s when:
- you want to include a file whose collocation with the including file is fixed and
- you are building on a POSIX system or with VC++ and
- you wish to avoid ambiguity about which file will be included.
It is always easy to provide an example of where your code-base contains an error and where this subsequently causes a hard-to-diagnose failure. However, even if your project is fault-free, it can be abused by a third party if you rely on absolute paths to specify files that are located relative to one another.
For example, consider the following project layout:
./your_lib/include/foo/header1.h
./your_lib/include/bar/header2.h
./their_lib/include/bar/header2.h
How should your_lib/include/foo/header1.h include your_lib/include/bar/header2.h? Let's consider two options:
-
#include <bar/header2.h>
Assuming both your_lib/include and their_lib/include are cited as header search paths (e.g. using GCC's
-I
or-isystem
options), then the choice of which header2.h will be chosen is sensitive to the order in which those two paths are searched. -
#include "../bar/header2.h"
The first location in which the compiler will search is the location of your_lib/include/foo/header1.h, which is your_lib/include/foo/. It will first try your_lib/include/foo/../bar/header2.h which reduces to your_lib/include/bar/header2.h where it will find the correct file. The header search paths will not be used at all and there is little room for ambiguity.
I would strongly recommend option 2) in this case for reasons given.
In response to some arguments in other answers:
-
@andrew-grant says:
...it makes it very clear what namespace or module the header file belongs to.
Maybe. But relative paths can be interpreted as "in this same module". They provide clarity in the case that there are multiple directories with the same name located in different modules.
-
@bk1e says:
...it will often work by accident...
I'd argue that relative paths will only work by accident in very rare cases where the project was broken from the start and could easily be fixed. To experience such a name collision without causing a compiler error seems unlikely. A common scenario is where a dependent project's file includes one of your headers which includes another of your headers. Compiling your test suite should result in a "No such file or directory" error when compiled in isolation from that dependent project.
-
@singlenegationelimination says
...that's not portable and the standard does not support it.
The ISO C standard may not specify all the details of the systems under which a program is compiled or run. That does not mean that they are unsupported, merely that the standard doesn't over-specify the platforms on which C will run. (The distinction between how
""
and<>
are interpreted on common modern systems likely originates in the POSIX standard.) -
@daniel-paull says
the ".." assumes relative location and is fragile
Fragile how? Presumably sensitive to the collocation of the two files. Thus "
..
" should only (and always) be used when the author of the including file controls their location.
Solution 4:
IANALL, but I don't think you should be putting ..
's in actual C or C++ source files, because that's not portable and the standard does not support it. This is similar to using \
's on Windows. Only do it if your compiler can't work with any other method.