Seperate .h and .cpp files for EACH C++ class?


#1

Hi guys,

just a quickie - is it the modern reccomended C++ way to place each C++ class in its own seperate header and implementation file ??

JUCE uses this approach of course and I can see many advantages for this but it naturally means having tons of seperate files, instead of less - where - say one bundles many related classes into the same file.

Whats the reccomended thing these days >

Nonchai


#2

I do it that way because when you’ve got a lot of code, it makes things easier to find. (I don’t stick 100% to one-class-per-file though, there are a few files that contain a couple of closely-related classes).


#3

do you think it has any effect on compilation times ?


#4

On my Intel i7-985 Extreme with SSD and 12GB RAM? I’m thinking no. A full rebuild of the Juce library takes about 35 seconds for me. Not that I am rebuilding the library very often.

Juce seems to be organized pretty well, I wouldn’t worry about it. If you’re having compilation problems then there is usually something you can do on your end (include less files or set up a precompiled header for example).


#5

“It depends”. :smiley:

First, I’d say that compilation times should be the least of your worries, even if your build is quite slow, and you should be working on those things that make your code more reliable and easier for you to understand first. From seeing other people trying to optimize builds, changes like “separate files” don’t result in earth-shattering changes to build times one way or the other - i.e. “you almost certainly wouldn’t notice”.

There is a way to keep compile times down, or at least prevent them from ballooning, and that involves strong separation of .h files by using forward references.

I think an example from my codebase will speak loudly here. Instance is my “god class” that lets you get to everything in my application. I fought having one for a long time, but the mess always piled up somewhere else so I went the other way and made it a struct![code]#include “rec/base/base.h” // Tiny include file everyone has to have.

class Components;
class Menus;
class Model;
class Target;
class Threads;
class Listeners;

struct Instance {
explicit Instance(DocumentWindow* window);
~Instance();

DocumentWindow* window_;

ptr components_; // ptr<> is very much like ScopedPtr.
ptr device_;
ptr player_;
ptr model_;
ptr menus_;
ptr target_;
ptr listeners_;
ptr threads_;

DISALLOW_COPY_ASSIGN_AND_EMPTY(Instance);
};[/code]Note how all the classes that actually do the work are forward-references and so file doesn’t include any other file except my “base.h”.

This means it’s extremely “light” to include this file Instance.h file because it doesn’t pull in other things. If you specifically need to know how some of these classes work, then you as the client of Instance will have to include their include files, but in fact nearly all the clients just use one or two of the parts. Only Instance.cpp needs to include the actual declaration for each of its component parts!

I tend to go further and completely hide the very existence of some classes from everyone else! For example, I have a class that runs a callback in a thread - except that I expose it to no one:[code]// MakeThread.h: you only get a function declaration here, very light!

// Create a thread that runs a Callback.
Thread* makeThread(const String& name, Callback* cb);

// MakeThread.cpp is the only place you can find the actual class definition.

namespace {

class CallbackThread : public juce::Thread {
public:
CallbackThread(const String& name, Callback* cb) { /* … */ }
// …
};

} // namespace

Thread* makeThread(const String& name, Callback* cb) { return new CallbackThread(name, cb); }[/code]

It isn’t just that you’ll cut compilation times a reasonable amount; and it isn’t just that your code is compartmentalized so that build errors affect a lot less of the code base (making them much easier to track down); it’s that your code is compartmentalized so that you the programmer can simply forget about the very existence of certain classes that you wrote and are finished because you simply never see them - it simply lessens the cognitive burden on you, the programmer.


#6

Very good points, this is the style that I like:

“ICore.h”

// Clients see this interface
class ICoreObject
{
public:
  virtual void publicOperation () = 0;
};

“Core.h”

#include "ICore.h"
// Other objects in the library "see" selected implementation details
class CoreObject : public ICoreObject
{
public:
  static CoreObject* New ();
  virtual ~CoreObject() { }
  virtual void implementationSpecificOperation () = 0;
};

“Core.cpp”

#include "Core.h"
// This class is completely private
class CoreObjectImp : public CoreObject
{
public:
  void publicOperation () { }
  void implementationSpecificOperation () { }

private:
  PrivateData m_data;
};

CoreObject* CoreObject::New ()
{
  return new CoreObjectImp;
}

The benefit here is that ALL of the includes for the declarations required for the implementation go in the .cpp file instead of the .h file. Nothing is exposed. As a convenience, every function in the implementation can be defined in the declaration, no need to have separate declarations and definitions (except in the public interface).


#7

very interesting and useful food for thought here.

thanks for all the replies chaps.