2

I've recently picked up C++ as part of my course, and I'm trying to understand in more depth the partnership between headers and classes. From every example or tutorial I've looked up on header files, they all use a class file with a constructor and then follow up with methods if they were included. However I'm wondering if it's fine just using header files to hold a group of related functions without the need to make an object of the class every time you want to use them.

//main file
#include <iostream>
#include "Example.h"
#include "Example2.h"

int main()
{
    //Example 1
    Example a; //I have to create an object of the class first
    a.square(4); //Then I can call the function

    //Example 2
    square(4); //I can call the function without the need of a constructor

    std::cin.get();
}

In the first example I create an object and then call the function, i use the two files 'Example.h' and 'Example.cpp'

  //Example1 cpp
    #include <iostream>
    #include "Example.h"

void Example::square(int i)
{
    i *= i;
    std::cout << i << std::endl;
}
//Example1 header
class Example
{
public:
    void square(int i);
};

In example2 I call the function directly from file 'Example2.h' below

//Example2 header
void square(int i)
{
    i *= i;
    std::cout << i;
}

Ultimately I guess what I'm asking is, if it's practical to use just the header file to hold a group of related functions without creating a related class file. And if the answer is no, what's the reason behind that. Either way I'm sure I've over looked something, but as ever I appreciate any kind of insight from you guys on this!

  • 2
    Header files are just a way to tell the compiler what things are available in other compilation units/libraries. You can put any kind of declaration in them and some definitions too. – Galik Oct 04 '18 at 12:05

4 Answers4

4

Of course, it's just fine to have only headers (as long as you consider the One Definition Rule as already mentioned).

You can as well write C++ sources without any header files.

Strictly speaking, headers are nothing else than filed pieces of source code which might be #included (i.e. pasted) into multiple C++ source files (i.e. translation units). Remembering this basic fact was sometimes quite helpful for me.

I made the following contrived counter-example:

main.cc:

#include <iostream>

// define float
float aFloat = 123.0;

// make it extern
extern float aFloat;

/* This should be include from a header
 * but instead I prevent the pre-processor usage
 * and simply do it by myself.
 */
extern void printADouble();

int main()
{
  std::cout << "printADouble(): ";
  printADouble();
  std::cout << "\n"
    "Surprised? :-)\n";
  return 0;
}

printADouble.cc:

/* This should be include from a header
 * but instead I prevent the pre-processor usage
 * and simply do it by myself.
 *
 * This is intentionally of wrong type
 * (to show how it can be done wrong).
 */

// use extern aFloat
extern double aFloat;

// make it extern
extern void printADouble();

void printADouble()
{
  std::cout << aFloat;
}

Hopefully, you have noticed that I declared

  • extern float aFloat in main.cc
  • extern double aFloat in printADouble.cc

which is a disaster.

Problem when compiling main.cc? No. The translation unit is consistent syntactically and semantically (for the compiler).

Problem when compiling printADouble.cc? No. The translation unit is consistent syntactically and semantically (for the compiler).

Problem when linking this mess together? No. Linker can resolve every needed symbol.

Output:

printADouble(): 5.55042e-315
Surprised? :-)

as expected (assuming you expected as well as me nothing with sense).

Live Demo on wandbox

printADouble() accessed the defined float variable (4 bytes) as double variable (8 bytes). This is undefined behavior and goes wrong on multiple levels.

So, using headers doesn't support but enables (some kind of) modular programming in C++. (I didn't recognize the difference until I once had to use a C compiler which did not (yet) have a pre-processor. So, this above sketched issue hit me very hard but was really enlightening for me, also.)

IMHO, header files are a pragmatic replacement for an essential feature of modular programming (i.e. the explicit definion of interfaces and separation of interfaces and implementations as language feature). This seems to have annoyed other people as well. Have a look at A Few Words on C++ Modules to see what I mean.

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • Wow thank you for your take on the answer, like the other answers it's helped make a lot of sense of quite a few things I was trying to get my head around. But I especially liked your detail answer and different approach to it, thanks again! – AspiringNewbie Oct 04 '18 at 12:25
3

C++ has a One Definition Rule (ODR). This rule states that functions and objects should be defined only once. Here's the problem: headers are often included more than once. Your square(int) function might therefore be defined twice.

The ODR is not an absolute rule. If you declare square as

//Example2 header
inline void square(int i)
// ^^^
{
    i *= i;
    std::cout << i;
}

then the compiler will inform the linker that there are multiple definitions possible. It's your job then to make sure all inline definitions are identical, so don't redefine square(int) elsewhere.

Templates and class definitions are exempt; they can appear in headers.

MSalters
  • 159,923
  • 8
  • 140
  • 320
2

C++ is a multi paradigm programming language, it can be (at least):

  • procedural (driven by condition and loops)
  • functional (driven by recursion and specialization)
  • object oriented
  • declarative (providing compile-time arithmetic)

See a few more details in this quora answer.

Object oriented paradigm (classes) is only one of the many that you can leverage programming in C++.
You can mix them all, or just stick to one or a few, depending on what's the best approach for the problem you have to solve with your software.

So, to answer your question:
yes, you can group a bunch of (better if) inter-related functions in the same header file. This is more common in "old" C programming language, or more strictly procedural languages.

That said, as in MSalters' answer, just be conscious of the C++ One Definition Rule (ODR). Use inline keyword if you put the declaration of the function (body) and not only its definition (templates exempted). See this SO answer for description of what "declaration" and "definition" are.

Additional note

To enforce the answer, and extend it to also other programming paradigms in C++, in the latest few years there is a trend of putting a whole library (functions and/or classes) in a single header file.
This can be commonly and openly seen in open source projects, just go to github or gitlab and search for "header-only":

roalz
  • 2,581
  • 3
  • 24
  • 41
  • Thank you very much for your detailed answer, it's helped make sense of my question but surprisingly a lot of other things I was unsure about. I also appreciate the extra source links especially to the library headers, I'm going to take a more detailed look into that, thank you! – AspiringNewbie Oct 04 '18 at 12:28
0

The common way is and always has been to put code in .cpp files (or whatever extension you like) and declarations in headers. There is occasionally some merit to putting code in the header, this can allow more clever inlining by the compiler. But at the same time, it can destroy your compile times since all code has to be processed every time it is included by the compiler.

Finally, it is often annoying to have circular object relationships (sometimes desired) when all the code is the headers.

Some exception case is Templates. Many newer "modern" libraries such as boost make heavy use of templates and often are "header only." However, this should only be done when dealing with templates as it is the only way to do it when dealing with them.

Some downsides of writing header only code If you search around, you will see quite a lot of people trying to find a way to reduce compile times when dealing with boost. For example: How to reduce compilation times with Boost Asio, which is seeing a 14s compile of a single 1K file with boost included. 14s may not seem to be "exploding", but it is certainly a lot longer than typical and can add up quite quickly. When dealing with a large project. Header only libraries do affect compile times in a quite measurable way. We just tolerate it because boost is so useful.

Additionally, there are many things which cannot be done in headers only (even boost has libraries you need to link to for certain parts such as threads, filesystem, etc). A Primary example is that you cannot have simple global objects in header only libs (unless you resort to the abomination that is a singleton) as you will run into multiple definition errors. NOTE: C++17's inline variables will make this particular example doable in the future.

To be more specific boost, Boost is library, not user level code. so it doesn't change that often. In user code, if you put everything in headers, every little change will cause you to have to recompile the entire project. That's a monumental waste of time (and is not the case for libraries that don't change from compile to compile). When you split things between header/source and better yet, use forward declarations to reduce includes, you can save hours of recompiling when added up across a day.

Helping Bean
  • 139
  • 7