11

Even though I have a decent bit of professional experience with object oriented programming in Java and a basic familiarity with C, I've run into a bit of a mental block with C++ that I was hoping other developers could help me shine a light on.

In Java, almost every time I define a class I naturally create a new .java file. Of course there are the exceptions of internal and anonymous classes, but by and large a new class means a new file. With new classes come packages to organize them and so on, and as I've started dabbling in C++ I've taken that same approach of one .java file per class and extended it to one .hpp file and one .cpp file per class. However, it seems that there are a lot of problems with this approach. The one that's been on my mind lately is like this:

Say I have some kind of game where there is a class representing the game world and a class to represent the units. If the game world class has a method to get the unit at a location, that's all well and good. However, if I want to have a method for retrieving the containing world from the unit, the include directives won't resolve in the way that I'm used to building projects since each header includes the other. I know that there of course has to be a clean solution, and I want to know if some more experienced developers may have ideas on resources about C++ project organization, or on handling the specific construct I mentioned.

6
  • 1
    Looks like this question hasn't been asked here before, despite a plethora of similar questions. I did find this one on Stack Overflow, however: C++ - Forward declaration
    – user22815
    Jul 1, 2016 at 2:29
  • I was definitely afraid of it being marked a duplicate, though if I'd found what I was looking for I wouldn't have asked. I'm aware of forward declaration, but it seems like a hacky solution. For all I know it may be the best one. When dealing with classes, anything declared using a forward declaration has to be a pointer though, since sizes naturally can't be calculated. Isn't it bad style in C++ to use raw pointers, or is it a necessity?
    – SaxSalute
    Jul 1, 2016 at 3:23
  • 2
    Do you use guards in your header files?
    – cup
    Jul 1, 2016 at 5:23
  • 2
    Google for "C++ file organization", that will bring you to lots of tutorials. Moreover, if you really want to learn in-depth how and why to organize your C++ code in a scalable fashion, get a copy of John Lakos' book "large scale C++ software design".
    – Doc Brown
    Jul 1, 2016 at 6:40
  • 1
    I think that using one file per class is often a very bad idea in C++. Jul 1, 2016 at 16:49

4 Answers 4

10

In C++ you have full flexibility how you want to organize your files. But you have to get accustomed to this freedom to make the good choices:

  • A first practice is to have include guards in headers, in order to avoid that due to shared dependencies, the same header gets included multiple times.

  • A second is discipline: make headers self-sufficient and always include in them all the required header dependencies. Make no assumptions about what the including code should have included else. Do not worry about mutual dependency: this is usually handled through forward declarations and separating declaration from implementation

  • A third is to decide and use consistently what granularity you want for your headers: do you want a header per class (if you're used to it, why not continue) ? Or do you prefer a header for higher logical units (e.g. class and its auxiliaries) ?

  • Then come the question of directories and namespaces (perhaps something that comes closes to the notion of java "packages" for the moment): do you keep files of different namespaces in different directories or not ? Note that this reflection may also be loosely related to the question of whether making a library subproject or not.

  • Don't forget that you development environment could also influence the chosen directory structure (example or example).

For the future, it could be interesting to keep an eye on the concept of C++ modules. It's not yet standard, so you shouldn't build your current approach on it now. Yet it is interesting, as it intends to ease componentization of code using a java package like fashion, that would gradually get rid of headers. But it will still let you a big freedom for the file organisaton.

8

Say I have some kind of game where there is a class representing the game world and a class to represent the units. If the game world class has a method to get the unit at a location, that's all well and good.

I guess you mean something like this

// Gamworld.hpp
#pragma once

#include "Unit.hpp"

class Gameworld
{
     public:
       Unit GetUnit();
};

However, if I want to have a method for retrieving the containing world from the unit, the include directives won't resolve in the way that I'm used to building projects since each header includes the other.

No, you do not need to include the Gameworld.hpp in Unit.hpp if you use a forward declaration and use only pointers, references or shared pointers there:

// Unit.hpp
#pragma once

class Gameworld;

class Unit
{  
      Gameworld *world;

   public:
       Gameworld *GetWorld();
}

Only include Gameworld.hpp in Unit.cpp:

// Unit.cpp
#include "Gameworld.hpp"

Gameworld *Unit::GetWorld()
{
    return world;
}

I hope you get the basic idea. Though a bit outdated, I heavily recommend to get a copy of Lakos' book "Large Scale C++ Software Design", it will explain all the gory details and strategies about physical C++ file layout you need to know.

4
  • The #endif needs to go to the end of the header file, or it doesn’t protect the file at all.
    – gnasher729
    Sep 15, 2021 at 19:49
  • @gnasher729: thanks, I took this as a chance to simplify my old answer a little bit.
    – Doc Brown
    Sep 15, 2021 at 20:18
  • #pragma once is a non-standard directive.
    – Zakk
    Sep 15, 2021 at 21:34
  • 2
    @Zakk but it's a non-standard directive understood by all the currently-maintained implementations that I'm aware of
    – Caleth
    Sep 16, 2021 at 8:49
6

Your options for resolving circular dependencies are:

  1. forward declaration

    Yes, this really is commonly used and isn't considered a hack.

    Note that only the public interfaces of your two interdependent types need the forward declaration: if the implementation is out-of-line, it's a separate translation unit, and there's no problem with each including the other's header.

    Note also that even if you put everything in a single header as Basile suggests, it's entirely normal to use forward-declaration even when the real declaration follows later in the same header.

  2. type erasure

    Give the unit a pointer or reference to an abstract base class of the real game class, which itself has no dependency on the unit type. This does force the caller to cast, though.

    You could use void* instead of introducing an ABC, but that's even uglier.

The better option, though, is to break the dependency entirely if possible.

When do you actually have a unit object, but not the game context? Can't you just pass it in instead of making every unit object bigger to contain a reference to state the caller already had?

3

In C++11 (and other dialects of C++, and in C) you can have several translation units for a given program. So your program could be made of foo.cc, bar.cc, gee.cc C++ source files compiled separately and linked together. If using GCC (e.g. on Linux), you would compile your program with e.g.

 g++ -Wall -g -c foo.cc
 g++ -Wall -g -c bar.cc
 g++ -Wall -g -c gee.cc

Above: -Wall asks for all warnings, -g asks for debug information, -c asks only for compilation without linking.

(so foo.cc gets compiled into the foo.o object file, bar.cc into bar.o, and so on) and later link it (into a progbin executable) with

 g++ -g foo.o bar.o gee.o -o progbin

In practice you won't type the commands each time, but you would use a builder like make to drive and run the above commands (see this and that examples of Makefile-s). The commands above are for a simple case, read the documentation about Invoking GCC for more (and order of arguments to g++ matters a lot!). You often want to add -Idir options for compiling with some include directory dir, and -Ldir & -lname options for linking (add a library directory dir, and use a given library name).

You want to avoid writing (and copy/pasting) the same declarations multiple times in each source file. You may (and sometimes need to) have forward declarations and opaque pointer types (you want to use smart pointers extensively), read about the PIMPL idiom. So you'll have a common myprog.h header file (or several header files) which would be #include-d by every source file foo.cc, bar.cc etc....).

There is no obligation regarding header and source files. In particular (contrarily to Java) you can (and often want) to have several declarations in a header file, and several class and function definitions (& implementation) in a source file.

So for a small (less than 50KLOC) project, I would recommend having one single common header file (with all the declarations) which might be precompiled and a few source files. But that is mostly a matter of opinion: I like having a single header file (of a few thousand lines) and few source files (of a few thousand lines) but some people like having many small header files and small source files (that makes the compilation time slower).

Once you have debugged your program (with the help of gdb and valgrind, perhaps using some instrumentation options by compiling and linking with -fsanitize=address or -fsanitize=undefined or other -fsanitize= options), you might want to profile or benchmark it. For profiling, compile with -pg after -g and use gprof and/or oprofile. For benchmarking, you want to ask the compiler to optimize with -O2 .

In practice, look into existing free software code (e.g. on github) for concrete examples.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.