Pros & Cons of putting all code in Header files in C++?
Asked Answered
I

18

60

You can structure a C++ program so that (almost) all the code resides in Header files. It essentially looks like a C# or Java program. However, you do need at least one .cpp file to pull in all the header files when compiling. Now I know some people would absolutely detest this idea. But I haven't found any convincing downsides of doing this. I can list some advantages:

[1] Faster compile times. All header files only get parsed once, because there is only one .cpp file. Also, one header file cannot be included more than once, otherwise you will get a build break. There are other ways of achieving faster compiles when using the alternate approach, but this is so simple.

[2] It avoids circular dependencies, by making them absolutely clear. If ClassA in ClassA.h has a circular dependency on ClassB in ClassB.h, I have to put a forward reference & it sticks out. (Note that this is unlike C# & Java where the compiler automatically resolves circular dependencies. This encourages bad coding practices IMO). Again, you can avoid circular dependencies if your code was in .cpp files, but in a real-world project, .cpp files tend to include random headers until you can't figure out who depends on whom.

Your thoughts?

Ives answered 11/10, 2008 at 8:44 Comment(4)
If you make it a policy to use forward declares as much as possible. Not only will a project consisting of lots of .cpp file compile faster, but you almost never have to worry about circular dependencies. Basically, if you don't need the full definition in the header, use a forward declare.Stenopetalous
+1 This is a good question, as this programming practice is even used in some big projects. (www.ogre3D.com for ex.)Kentiggerma
Point 1 ignores the prospect of using more than one core/machine to compile your project. Distributing compilations of multiple cpp files across multiple cores can beat the compilation of the program as a single cpp file being compiled only on one core.Agminate
What about performance ? Can't the compiler make better optimisations if it sees all the code in one go ? (e.g. inlining, etc..)Fumed
B
40

Reason [1] Faster compile times

Not in my projects: source files (CPP) only include the headers (HPP) they need. So when I need to recompile only one CPP because of a tiny change, I have ten times the same number of files that are not recompiled.

Perhaps you should break down your project in more logical sources/headers: A modification in class A's implementation should NOT need the recompilation of implementations of class B, C, D, E, etc..

Reason[2] It avoids circular dependencies

Circular dependencies in code?

Sorry, but I have yet to have this kind of problem being a real problem: Let's say A depends on B, and B depends on A:

struct A
{
   B * b ;
   void doSomethingWithB() ;
} ;

struct B
{
   A * a ;
   void doSomethingWithA() ;
} ;

void A::doSomethingWithB() { /* etc. */ }
void B::doSomethingWithA() { /* etc. */ }

A good way to resolve the problem would be to break down this source into at least one source/header per class (in a way similar to the Java way, but with one source and one header per class):

// A.hpp

struct B ;

struct A
{
   B * b ;
   void doSomethingWithB() ;
} ;

.

// B.hpp

struct A ;

struct B
{
   A * a ;
   void doSomethingWithA() ;
} ;

.

// A.cpp
#include "A.hpp"
#include "B.hpp"

void A::doSomethingWithB() { /* etc. */ }

.

// B.cpp
#include "B.hpp"
#include "A.hpp"

void B::doSomethingWithA() { /* etc. */ }

Thus, no dependency problem, and still fast compile times.

Did I miss something?

When working on "real-world" projects

in a real-world project, cpp files tend to include random headers until you can't figure out who depends on whom

Of course. But then if you have time to reorganize those files to build your "one CPP" solution, then you have time to clean those headers. My rules for headers are:

  • break down header to make them as modular as possible
  • Never include headers you don't need
  • If you need a symbol, forward-declare it
  • only if the above failed, include the header

Anyway, all headers must be self-sufficient, which means:

  • An header include all needed headers (and only needed headers - see above)
  • an empty CPP file including one header must compile without needing to include anything else

This will remove ordering problems and circular dependencies.

Is compile times an issue? Then...

Should compile time be really an issue, I would consider either:

Conclusion

What you are doing is not putting everything in headers.

You are basically including all your files into one and only one final source.

Perhaps you are winning in terms of full-project compilation.

But when compiling for one small change, you'll always lose.

When coding, I know I compile often small changes (if only to have the compiler validate my code), and then one final time, do a full project change.

I would lose a lot of time if my project was organized your way.

Bosquet answered 11/10, 2008 at 15:14 Comment(0)
M
27

I disagree with point 1.

Yes, there is only one .cpp and the built time from scratch is faster. But, you rarely build from scratch. You make small changes, and it would need to recompile the whole project each time.

I prefer doing it the other way around:

  • keep shared declarations in .h files
  • keep definition for classes that are only used in one place in .cpp files

So, some of my .cpp files start looking like Java or C# code ;)

But, 'keeping stuff in .h' approach is good while designing the system, because of point 2. you made. I usually do that while I'm building the class hierarchy and later when code architecture becomes stable, I move code to .cpp files.

Minimalist answered 11/10, 2008 at 8:50 Comment(2)
Disagreement isn't really strong enough. This isn't open to debate: point 1 is flat out wrong for the reasons you mentioned.Alake
@Konrad: one can argue that having a compiler that has support for pre-compiled headers (MSVC and GCC) doesn't really rebuild all the header files - only the inter-dependent ones - just like .cpp file approach. However, setting that up would meant having a separate PCH file for each .h file.Avina
G
17

You are right to say that your solution works. It may even have no cons for your current project and developing environment.

But...

As others stated, putting all your code in header files forces a full compilation every time you change one line of code. This may not be an issue yet but your project may grow large enough to the point compilation time will be an issue.

Another problem is when sharing code. While you may not be directly concerned yet, it is important to keep as much code as possible hidden from a potential user of your code. By putting your code into the header file, any programmer using your code must look the whole code, while there are just interested in how to use it. Putting your code in the cpp file allows to only deliver a binary component (a static or dynamic library) and its interface as header files, which may be simpler in some environment.

This is a problem if you want to be able to turn your current code into a dynamic library. Because you don't have a proper interface declaration decoupled from the actual code, you won't be able to deliver a compiled dynamic library and its usage interface as readable header files.

You may not have these issues yet, that's why I was telling that your solution may be ok in your current environment. But it is always better to be prepared to any change and some of these issues should be addressed.

PS: About C# or Java, you should keep in mind that these languages are not doing what you say. They are actually compiling files independently (like cpp files) and stores the interface globally for each file. These interfaces (and any other linked interfaces) are then used to link the whole project, that's why they are able to handle circular references. Because C++ does only one compilation pass per file, it is not able to globally store interfaces. That's why you are required to write them explicitely in header files.

Gerous answered 11/10, 2008 at 9:38 Comment(0)
F
13

The obvious downside to me is that you always have to build all the code at once. With .cpp files, you can have separate compilation, so you're only rebuilding bits that have really changed.

Frisket answered 11/10, 2008 at 8:47 Comment(3)
Ah good catch. So there won't be any "incremental" builds, and in that sense, compile times will be slower. In practice though, I find building pretty fast, since my code is broken up into separate static libs.Ives
That wont change anything. If you have all of your code in your .h file and then you make a modification - every .cpp and .h (technically: .cpp) that includes it has to be rebuilt.Keitel
@Nelson: Exactly why I disagree with having all code in .h files.Frisket
K
12

You misunderstand how the language was intended to be used. .cpp files are really (or should be with the exception of inline and template code) the only modules of executable code you have in your system. .cpp files are compiled into object files that are then linked together. .h files exist solely for forward declaration of the code implemented in .cpp files.

This results in quicker compile time and smaller executable. It also looks considerably cleaner because you can get a quick overview of your class by looking at its .h declaration.

As for inline and template code - because both of these are used to generate code by the compiler and not the linker - they must always be available to the compiler per .cpp file. Therefore the only solution is to include it in your .h file.

However, I have developed a solution where I have my class declaration in a .h file, all template and inline code in a .inl file and all implementation of non template/inline code in my .cpp file. The .inl file is #included at the bottom of my .h file. This keeps things clean and consistent.

Keitel answered 11/10, 2008 at 9:14 Comment(2)
The .inl (or .tcc, in GNU libstdc++) is a Best Practice, in my opinion. +1!Frisket
If people only did things the way the language was intended to be used, there'd be no template meta-programming. Oh, hang on, I'm not sure that'd be a bad thing ;-)Mendacious
W
6

One disadvantage of your approach is you can't do parallel compilation. You might think you're getting a faster compile now, but if you have multiple .cpp files you can build them in parallel on either multiple core on your own machine or using a distributed build system like distcc or Incredibuild.

Wintery answered 12/10, 2008 at 16:25 Comment(0)
K
3

You might want to check out Lazy C++. It allows you to place everything in a single file and then it runs prior to compilation and splits the code into .h and .cpp files. This might offer you the best of both worlds.

Slow compile times are usually due to excessive coupling within a system written in C++. Maybe you need to split code into subsystems with external interfaces. These modules could be compiled in separate projects. This way you can minimize the dependency between different modules of the system.

Kathernkatheryn answered 11/10, 2008 at 12:56 Comment(0)
S
3

I like to think about separation of .h and .cpp files in terms of interfaces and implementations. The .h files contain the interface descriptions to one more classes and the .cpp files contain the implementations. Sometimes there are practical issues or clarity that prevent a completely clean separation, but that's where I start. For example, small accessor functions I typically code inline in the class declaration for clarity. Larger functions are coded in the .cpp file

In any case, don't let compilation time dictate how you will structure your program. Better to have a program that's readable and maintainable over one that compiles in one 1.5 mintues instead of 2 minutes.

Strutting answered 11/10, 2008 at 14:6 Comment(1)
+1 for readability and maintainability being more important. Besides which, compile times are less of an issue than they were in 2008 because computers have become a lot faster since, and even then compile times are only really important for larger projects.Jared
Z
3

One thing you're giving up that I would have a hard time living without is anonymous-namespaces.

I find that they're incredibly valuable for defining class-specific utility functions that should invisible outside the class's implementation file. They're also great for holding any global data that should be invisible to the rest of the system,like a singleton instance.

Zeiger answered 11/10, 2008 at 15:55 Comment(0)
S
3

You're going outside of the design scope of the language. While you may have some benefits, it's going to eventually bite you in the butt.

C++ is designed for h files having declarations, and cpp files having implementations. Compilers are built around this design.

Yes, people debate whether that's a good architecture, but it's the design. It's better to spend your time on your problem than reinventing new ways to design C++ file architecture.

Salacious answered 11/10, 2008 at 16:23 Comment(0)
A
2

Well, as many have pointed out there is a lot of cons to this idea, but to balance out a bit and provide a pro, I would say that having some library code entirely in headers makes sense, since it will make it independent of other settings in the project it is used in.

For example, if one is trying to make use of different Open Source libraries they can be set to use different approaches for linking to your program - some may use the operating system's dynamically loaded library code, others is set to be statically linked; some may be set to use multithreading, while others is not. And it may very well be an overwhelming task for a programmer - especially with a time constraint - to try to sort these incompatible approches out.

All of that is however not an issue when using libraries that are contained entirely in headers. "It just works" for a reasonable well-written library.

Adamo answered 11/10, 2008 at 16:49 Comment(0)
D
2

I believe that unless you are using MSVC's pre-compiled headers, and you are using a Makefile or other dependency based build system, having separate source files should compile faster when building iteratively. Since, my development is almost always iterative, I care more about how fast it can recompile the changes I made in file x.cpp than in the twenty other source files that I didn't change. Additionally, I make changes much more frequently to the source files than I do to the APIs so they change less frequently.

Regarding, circular dependencies. I would take paercebal's advice one step farther. He had two classes that had pointers to each other. Instead, I run into the case more frequently where one class requires another class. When this happens, I include the header file for the dependency in the header file of the other class. An example:

// foo.hpp
#ifndef __FOO_HPP__
#define __FOO_HPP__

struct foo
{
   int data ;
} ;

#endif // __FOO_HPP__

.

// bar.hpp
#ifndef __BAR_HPP__
#define __BAR_HPP__

#include "foo.hpp"

struct bar
{
   foo f ;
   void doSomethingWithFoo() ;
} ;
#endif // __BAR_HPP__

.

// bar.cpp
#include "bar.hpp"

void bar::doSomethingWithFoo()
{
  // Initialize f
  f.data = 0;
  // etc.
}

The reason that I include this, which is slightly unrelated to circular dependencies, is that I feel there are alternatives to including header files willy-nilly. In this example the struct bar source file does not include the struct foo header file. This is done in the header file. This has an advantage in that a developer using bar does not have to know about any other files that the developer would need to include to use that header file.

Dermis answered 11/10, 2008 at 17:58 Comment(0)
T
2

One problem with code in headers is that it must be inlined, otherwise you will have multiple-definition problems when linking multiple translation units that include that same header.

The original question specified that there was only ever a single cpp in the project, but that is not the case if you're creating a component destined for a reusable library.

Therefore, in the interests of creating the most reusable and maintainable code as possible, only put inlined and inlinable code in header files.

Twinflower answered 12/10, 2008 at 2:31 Comment(1)
Note that inline really means 'inline linkage', not 'inline function'. It does act as a hint to the compiler that inlining the function may be preferable, but it's not a guarantee and the compiler will choose what it thinks is best.Jared
S
1

The important philosophy of Object Oriented Programming lies in having data hiding leading to encapsulated classes with implementation hidden out from users. This is primarily to provide an abstraction layer where the users of a class primarily use the publicly accessible member functions for instance specific as well as static types. Then the developer of the class is free to modify the actual implementations provided the implementations are not exposed to the users. Even if the implementation is private and declared in a header file, changing the implementation will require all dependent codebase to re-compile. Whereas, if the implementation (definition of the member functions) are in a source code (non-header file), then the library gets changed, and the dependent codebase needs to re-link with the revised version of the library. If that library is dynamically linked one like a shared library then keeping the function signature (interface) same and implementation changed does not require re-linking also. Advantage? Of course.

Stochmal answered 16/4, 2014 at 8:29 Comment(0)
M
0

static-or-global-variable kludges even less transparent, perhaps un-debuggable.

for example counting the total number of iterations for analysis.

In MY kludged files putting such items at the top of the cpp file makes them easy to find.

By "perhaps un-debuggable", I mean that routinely I will put such a global into the WATCH window. Since it's always-in-scope the WATCH window can always get to it no matter where the program counter happens to be right now. By putting such variables outside a {} at the top of a header file you would let all downstream code "see" them. By putting them INSIDE a {}, offhand I would think the debugger will no longer consider them "in-scope" if your program-counter is outside the {}. Whereas with the kludge-global-at-Cpp-top, even though it might be global to the extent of showing up in your link-map-pdb-etc, without an extern-statement the other Cpp files can't get to it, avoiding accidental coupling.

Mosher answered 11/10, 2008 at 9:7 Comment(0)
I
0

One thing nobody has brought up is that compiling large files requires a lot of memory. Compiling your whole project at once would require such a huge memory space that it just isn't feasible even if you could put all code in headers.

Ignatzia answered 30/10, 2008 at 1:16 Comment(0)
G
0

If you are using template classes, you have to put the whole implementation in the header anyways...

Compiling the whole project in one go (through a single base .cpp file) should allow something like "Whole Program Optimisation" or "Cross-Module Optimisation", which is only available in a few advanced compilers. This is not really possible with a standard compiler if you are precompiling all your .cpp files into object files, then linking.

Grog answered 19/11, 2008 at 5:23 Comment(0)
G
0

Code in headers can be convenient for the coding process. Also, the ability to precompile improves efficiency. One side effect is that overall binary size generally increases as the amount of code in headers increases, e.g., if the headers are used across multiple libs/exes.

However, C++20 modules are the way now:
https://learn.microsoft.com/en-us/cpp/cpp/modules-cpp?view=msvc-170

Quote:

Modules eliminate or reduce many of the problems associated with the use of header files.

Tips for using modules:
https://github.com/MicrosoftDocs/cpp-docs/blob/main/docs/cpp/tutorial-named-modules-cpp.md

Note that for modules (unlike for precompiled headers), having code in implementation files, as opposed to the module interface file, can have compilation-time benefits.

Regarding binary size (using Visual C++ as an example) module builds produce object (.obj) files (as well as other files such as the interface .ifc file), so binary size proportionality with interface file source code size is less of an issue.
https://lists.isocpp.org/sg15/att-1346/C___20_Modules_Build_in_Visual_Studio.pdf

Gregorio answered 10/8, 2023 at 20:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.