Examples: Why is implementation code in header files?

Ok I am not a wizard in C++, but I thought header files contained the class definitions and the .cpp files contained the implementation, however having gone through a few of the JUCE provided examples, ALL code are in the header file!

Will somebody please explain why JUCE did that, as I thought this was kind of poor way of doing things.

Nothing says implementations have to be in .cpp files. It doesn’t really matter for small examples that all the code is in header files. They are examples, not tutorials about real life project files organization. In your own large projects you will probably want to do the .h/.cpp divide. For new plugin projects the Projucer generates the PluginProcessor.h/.cpp and PluginEditor.h/.cpp files for you.

What I thought, but still even though these JUCE provided examples are small, I think they would have set a better example, phun intended, to do it properly with better file organization.

1 Like

In fact, header only implementation is a coding style that is not so uncommon in the C++ world. There are quite a lot well known and good written C++ libraries out there that are purely implemented in headers.
Pros of the header-only style:

  • Easy to use: No linkage to the libraries or compilation of single compilation units (–> all the single .cpp files) are needed, a simple #include is enough to get it all working
  • Templated classes can only be implemented header-only.

Cons of the header-only style:

  • For really big projects, compilation time might increase. However, you might really write a lot of code until this gets really noticable

So in a real-world project, experienced coders will decide from case to case which style to use.

Thanks for that answer. Now I have a huge ambition to make a complete and quite unique polyphonic synthesizer with lots of advanced features not previously seen.

What do you recommend me to do then, headers only or separate it out?

Also, get used to seeing a lot more implementations in the same files as definitions as C++20 Modules start trickling out.

3 Likes

I followed the header-only style for a while, but recently switched to splitting things out (at least for bigger classes) on the recommendation of other C++ devs I was talking to. The advice was, the advantages of splitting will soon outweigh the advantages of keeping all source in single files, especially as projects get bigger.

FWIW thinking about how our SOUL codebase has ended up being structured (which is a good example at about 15 KLoC of extremely gnarly logic), a rough overview is:

  • the nature of writing a compiler means you have a lot of classes, mostly small. And almost all the small classes (certainly anything under about 200LoC) are inline
  • many bigger classes that are only used by a single piece of client code (i.e. are only included in a single .cpp file) are inline
  • classes that are widely used across the whole codebase and whose implementation is non-trivial get split into h/cpp
  • Some classes work best as an inline class and a public function that acts as the way you interact with it, rather than making the class itself public.

So probably about 80% inline is my guess (maybe more, actually)

As a beginner, my advice is: just write everything inline and worry about more important things. It’s easier to see what you’re doing if everything’s in one place, and by the time you have a reason to want header/cpp (i.e. you’ve written a lot of code) then you’ll probably have more of an understanding of how it all works.

I always find it painful to see code where beginners have laboriously split the most trivial classes out into pairs of files with just a few lines each, just because they heard somewhere that that’s what you’re “supposed” to do!

9 Likes

Jules, any reason why do you use „inline“ as a synonym for header declaration, this can be confusing for beginners, because there is also the c++ keyword inline (which requires header declaration)

1 Like

Functions defined within a class body are implicitly inline. That is, they have external linkage but the linker will deduplicate the definitions.

1 Like

I want my project code to be structured so it is more understandable and portable, however on the other hand my main priority is compiled code execution. The synth I want to create will rely heavily, more than usual, amounts of calculations, so every nano second saved will help. So my question is if functions defined in a class body are inline, does that mean they execute faster?

There’s basically no other way to know than to benchmark a release build of the code.

2 Likes

Anyway as a newcomer to this platform, and one who is just restarting out on C++ after more than 10 years hiatus, here is my two cents for what it is worth.

There is some inconsistency between some tutorials, and whether the code are split up into header and cpp files, or just in one header file which are most.

On top of that is the fact that Projucer, which newcomers might use more than more experienced developers, encourages newly added component classes to each be put into a new file, or even file pairs (header and cpp). As a matter of fact, unless I have missed something completely, Projucer does NOT have the feature to add a new component class into an existing file using the the menus, whether it be header or cpp file, unless one bypass the menus and add it manually.

I understand that for simplicity’s sake tutorials are all in one header file, but as much as I appreciate the tutorials and reference them a lot while I am getting started, I wish they were all consistent, and consistent with how Projucer works, which brings me to a feature request;

How about adding a few options to to the “Add” / “+” button?

When a user has selected a cpp or header file in the file explorer, there would then be at least one new option;

  1. “Add new Component class in this file”,

and if a pair of matching named files (header and cpp) are detected, then also this option;

  1. “Add new Component class class split between this pair of cpp and header files”

On a side note, it was not clear to me when I started, and even the most helpful “The Audio Programmer”, who has some excellent Youtube tutorials, also was unclear, that if you want to add a new Component class in existing project, you should click “Source” first or the newly created file or files, will be placed outside group of files and has to be dragged back in.

I am going to add the “feature request” part of this post to the “Feature Request” section.

2 Likes

Good thread. I had the exact same question/observations myself, being away from C++ programming for a number of years. In fact, one of my first experiments after working through a few tutorials with everything in the .h file was to try splitting those tutorials into .h/.cpp implementations - which worked and was a lot easier for me to understand.

I guess I’m just used to doing it that way - but I also have a massive project (originally written in Carbon and PowerPlant) that I’m going to work on porting to JUCE and it’s all in .h/.cpp pairs. I was worried that something had changed in general in C++, but then I assumed (rightfully it seems) that this was being done in the simpler tutorials for brevity.

Now, I have a lot of catching up to do with newer C++, lambda functions, many keywords I’ve never seen before! Fun!

The header-only approach has become popular for some libraries because it tends to be a pain to add .cpp files to build, or worse, add prebuilt binary libraries into a project. Standard C++ modules might fix that at some point, but I suspect general availability of that feature is something like 5 years away from now.

1 Like

This is confusing to me. I avoid header-only coding because of linker error LNK2005. Ie. if I write out a function in a header file and then include it in multiple cpp files, the linker complains that I have multiple definitions of the same function. Is there any way around this, other than by separating header and cpp files?

Just mark a function inline and it’ll work fine

2 Likes

I see, I didn’t realize that inline worked this way. Thanks for sharing! Very useful information.