76

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.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • You seem to be aware of all the factors involved so this isn't really a question. – shoosh Feb 28 '09 at 01:40
  • 1
    I am checking whether there are benefits I've overlooked (because I hate the notation but suffer from it at work). I have less problem with the notation without the ".." in it. – Jonathan Leffler Feb 28 '09 at 01:59
  • This (using relative paths) tends to lead to enormous headaches when using a separate build dir. I.e cd yourproject; mkdir build; cd build; ../configure && make - If you are going to keep maintaining the code, it may be worth the time to fix that. – Tim Post Oct 28 '09 at 18:32
  • Believe me, tinkertim, I am working on it. However, I do not have permission to make the change uniformly; it takes time to make this change (years - the guardians of the code base are horribly conservative). – Jonathan Leffler Oct 28 '09 at 18:35

8 Answers8

53

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.

Andrew Grant
  • 58,260
  • 22
  • 130
  • 143
32

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").

bk1e
  • 23,871
  • 6
  • 54
  • 65
  • 2
    yeah that thing using "projectname/include/header.h" was what i used in my last project (but started from include/ instead). i always put only one -I that points to the include/ path and then i include based on that. but i didn't analyze the ".." problems in depth yet. You have good points. – Johannes Schaub - litb Feb 28 '09 at 03:39
  • 1
    This seems to describe ways in which header paths are brittle in general, rather than relative paths in particular. One could easily come up with equivalent problem scenarios which don't involve `..`. – John McFarlane Jul 05 '19 at 10:17
  • "I wouldn't add any include path entries other than `-I.`..." It's far more common to add the 'include' directory, i.e. `-Iinclude`. Then your problems go away and `..` can be used in cases where it's appropriate. Having `lib` and `include` in your include paths this way seems like a code smell. – John McFarlane Jul 05 '19 at 10:17
10

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:

  1. #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.

  2. #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.

John McFarlane
  • 5,528
  • 4
  • 34
  • 38
  • What about option 3 `#include "bar/header2.h"`? Granted, it isn't very different from option 1, so you may have subsumed it under that option 1. – Jonathan Leffler Sep 03 '18 at 06:23
  • Yes, this is the same as option 1) except that first, the compiler would attempt to open *bar/header2.h* relative to the current path, *your_lib/include/foo/*. In other words, it would look for *your_lib/include/foo/bar/header2.h* which is not a valid path in this case. – John McFarlane Sep 03 '18 at 08:47
  • 2
    Thank you for this answer John. I was disappointed that the previous answers neglected the important points you raise. I do appreciate the point that specifying includes relative to the module name is beneficial for clarity, but really don't like the requirement this places on the project to use a specific "include directory". When a module uses source relative include paths there is no assumption that has to be made by the consumer about where this code exists, and no requirement for the project to add a module specific include path. – Tom Jan 26 '19 at 20:33
10

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • 3
    Good point. The standard doesn't even seem to guarantee that you can use forward-slashes. "The implementation provides unique mappings for sequences consisting of one or more nondigits (lex.name) followed by a period (.) and a single nondigit." It defines nondigit as basically [_a-zA-Z] or \uNNNN. – bk1e Feb 28 '09 at 03:26
  • 3
    This is true - but the software in question has been compiling on a wide range of platforms for a number of years (more than twenty of them), so it is more a theoretical than a practical liability. – Jonathan Leffler Feb 28 '09 at 07:59
  • 12
    Zero google hits for IANALL. I assume it's "I am not a language lawyer"? – Martijn Courteaux Jul 04 '17 at 22:36
  • In a Neuronal and Lexical Lingo? – Laurie Stearn Feb 14 '18 at 11:05
  • 1
    @singlenegationelimination please provide some evidence that the standard does not support `..`'s. – John McFarlane Jul 05 '19 at 09:23
2

Another issue on windows with relative paths is MAX_PATH. This will trigger compile issues when e.g. cross compiling for android and your path grows bigger than 260 in length.

MadSystem
  • 33
  • 6
  • 3
    [Starting in Windows 10, version 1607, `MAX_PATH` limitations have been removed from common Win32 file and directory functions. However, you must opt-in to the new behavior](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx) – phuclv Apr 03 '18 at 10:54
2

Think of your source tree as a nested namespace and the include path is allowing you to pull directories into the root of this namespace. The question is then one of forming a logical namespace for your code base irrespective of how the code is organised on disk.

I would avoid paths like:

  • "include/foo/bar.h" — the "include" seems illogical and redundant
  • "../foo/bar.h" — the ".." assumes relative location and is fragile
  • "bar.h" — unless bar.h is in the current directory, this pollutes the global namespace and is asking for ambiguities.

Personally, I tend to add a path like the following to my projects include path — "..;../..;../../..;../../../..".

This allows you to apply a sort of hiding rule to your #includes and allows some freedom of moving headers without breaking other code. Of course this is at the expense of introducing a risk of binding to the wrong header file if you are not careful as non-fully qualified names may be (or become over time) ambiguous.

I tend to fully qualify #includes in public headers so any third parties consuming my code do not need to add the "..;../..;../../..;../../../.." to their project — it's just a convenience for my private code and build system.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Daniel Paull
  • 6,797
  • 3
  • 32
  • 41
  • 1
    What's the purpose of `;` in your path? – Royi Oct 11 '17 at 21:30
  • 1
    The ";" is a path separator in windows, specifically, it's how you supply a set of directories to search to Visual Studio. – Daniel Paull Nov 21 '17 at 04:12
  • Could you elaborate. I'm not sure I understood. Thank You. – Royi Nov 21 '17 at 06:08
  • It means first search in "..", then search in "../..", then search in "../../..", etc – Daniel Paull Nov 23 '17 at 08:37
  • @DanielPaull can you explain why you believe that relative location is fragile? I'm especially interested given how much you recommend using relative location in your search paths. – John McFarlane Jul 05 '19 at 10:19
  • @JohnMcFarlane. Relative paths are fragile because bindings between files changes when you move them. Fragile in the sense that the binding breaks and needs to be fixes. If you bind from a known location then it is less fragile. – Daniel Paull Jul 08 '19 at 07:43
  • @DanielPaull don't bindings between files always change when you move them? Moves almost always results in compiler error. When they don't, this is due to a collision which can occur with non-relative paths in `#include`s and with changes to search paths. It seems to me that relative paths are a special case of a general fragility from which all includes and search paths suffer. – John McFarlane Jul 08 '19 at 10:32
  • @JohnMcFarlane. "It seems to me that relative paths are a special case of a general fragility from which all includes and search paths suffer." Yes, that's what I said. Relative binding, say, between package A and package B is fragile if they may be relocated relative to each other. Instead, us absolute paths, where the search roots are at the roots of the packages. Now you only need to update search roots when you shuffle projects around. – Daniel Paull Jul 08 '19 at 13:45
  • @DanielPaull On the contrary, I read your answer to imply that relative paths have a *specific* fragility but I just don't see it. Nobody's suggesting using a relative `#include` path in package A to include a file from package B. But if you wish to include a file in package A from another file in package A, then relative binding is not sensitive to changes in search path and so is arguably more robust. – John McFarlane Jul 09 '19 at 13:14
  • @JohnMcFarlane. Well, I can't help how you interpret my words. But if you read what I wrote in it's entirety then perhaps you wouldn't be searching for meaning that isn't there. I first noted "include/foo/bar.h" and then noted "../foo/bar.h" and then note "bar.h". It might be clear that "foo" is holds the headers for a given library and "bar.h" is a header for that library. If you were working within the "foo" library then including "../foo/bar.h" would just be silly, so a charitable interpretation would be that I'm talking about cross-library includes at that point. – Daniel Paull Jul 10 '19 at 06:20
  • @DanielPaull If a header, *include/other/header.h*, in the same library needs to include *include/foo/bar.h* then a robust directive would be `#include "../foo/bar.h"`. This way, no changes to the [search path](https://gcc.gnu.org/onlinedocs/cpp/Search-Path.html) (what you call the 'include path') can cause a different file called *bar.h* in a different directory called *foo* to be included by mistake. I'm struggling to find interpretations of the description of *../foo/bar.h* as 'fragile' and 'silly' that don't discourage this. – John McFarlane Jul 10 '19 at 11:30
  • "a robust directive would be #include "../foo/bar.h". You mean "../../foo/bar.h". lol. Find me a well known library that does this. Like a boost library. In the public public headers of the library you'll never see relative paths. If it's not done in our canonical examples of good library code, why not? What's wrong with your argument - go on, challenge your position... Have a look at the includes in Boost ASIO as an example. Lots of includes - none relative that I saw after examining a handful of headers. https://github.com/boostorg/asio/tree/develop/include/boost – Daniel Paull Jul 11 '19 at 03:10
  • @DanielPaull I'm not sure I do: *foo* and *other* are sibling directories and the first path tried will be relative to the path of the including file which is *include/other*, making *include/other/../foo/bar.h* which reduces to *include/foo/bar.h* which is your absolute path. And I'm well aware of the established convention. I'm looking for compelling reasons to justify it. – John McFarlane Jul 21 '19 at 23:05
  • If what you just wrote is not compelling enough of a reason then I don't know what you need. – Daniel Paull Jul 22 '19 at 05:29
1

Because then you place the file relative to the root of the project, and when you check it into source control and another developer checks it out to a different location on their local system things still work.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • 4
    I see this as an argument against absolute path names - but I wasn't asking about those (they're unequivocally bad AFAIAC). I'm not convinced that this is an argument for - or against - any of the variants of relative name. – Jonathan Leffler Feb 28 '09 at 07:57
0

Throwing out another argument for absolute (or "extended relative") include paths like #include "./lib/subdir/~/subsubsubsubdir/header.h"... When working on a really large project one may require lots of "-I./~" options on the link line. In a current project of mine the number of characters taken up by those options is nearly 40k which causes errors due to command line limitations.

Though usually I dislike the apparent inflexibility of that style of include path it can help avoid this sort of problem. For example a static library could provide its API through "#include "lib_api.h" and all other organization could be relative to that location and invisible to the user.

oclyke
  • 351
  • 1
  • 3
  • 12