I'm trying to find a clean way to separate implementation details in C++ header files in a big project in order to achieve better information hiding and reduce build time. The problem with C++ is that every time you change a private member declaration, your dependent classes must be rebuilt.
This is a solution I came up with. Is it any good?
The basic Idea is to include a part of the cpp file conditionally in the header. this part contains the implementation declarations and is included only when the implementation file includes the header. in case of the external classes, this details are excluded from header. so client and implementation see two different version of header file. internal declaration changes won't affect clients(no compilation of dependent classes) and headers won't include private details.
Here is the implementation:
HEADER
#pragma once
class Dependency
{
public:
Dependency(void);
~Dependency(void);
void Proc(void);
//PRIVATE Implementaion details stays private
#ifdef Dependency_PRIVATE_IMPELEMENTATION
#define Dependency_PRIVATE_MODE 1
#include "Dependency.cpp"
#undef Dependency_PRIVATE_MODE
#endif
};
CPP
#define Dependency_PRIVATE_IMPELEMENTATION
#include "Dependency.h"
#undef Dependency_PRIVATE_IMPELEMENTATION
#ifdef Dependency_PRIVATE_MODE
private:
int _privateData;
#else
#include <iostream>
Dependency::Dependency(void)
{
//This line causes a runtime exception, see client
Dependency::_privateData = 0;
}
Dependency::~Dependency(void)
{
}
void Dependency::Proc(void)
{
std::cout << "Shiny happy functions.";
}
#endif
CLIENT
#include "stdafx.h"
#include "Dependency.h"
#pragma message("Test.Cpp Compiled")
int _tmain(int argc, _TCHAR* argv[])
{
Dependency d;
d.Proc();
return 0;
//and how I have a run time check error #2, stack around d ?!!
}
It's a pretty interesting question, really. Managing dependencies is important for big projects because the build times ramp up can make even the simplest change daunting... and when it happens people will try to hack it to avoid the rebuild-of-death (tm).
Unfortunately, it does not work.
The Standard explicitly says that classes definitions appearing in different translation units (roughly, files) should obey the One Definition Rule (see § 3.2 One definition rule [basic.def.odr]).
Why ?
The problem is a matter of impedance, in a way. The definition of a class contains information on the class ABI (Application Binary Interface), most notably, how such a class is layed out in memory. If you have different layouts of the same class in various translation units, then when putting it altogether, it won't work. It's as if one TU was speaking German and the other Korean. They might be attempting to say the same thing, they just won't understand each other.
So ?
There are several ways to manage dependencies. The main idea is that you should struggle, as much as possible, to provide "light" headers:
Hum... What does it mean :x ?
Let's pick a simple example, shall we ?
#include "project/a.hpp" // defines class A
#include "project/b.hpp" // defines class B
#include "project/c.hpp" // defines class C
#include "project/d.hpp" // defines class D
#include "project/e.hpp" // defines class E
namespace project {
class MyClass {
public:
explicit MyClass(D const& d): _a(d.a()), _b(d.b()), _c(d.c()) {}
MyClass(A a, B& b, C* c): _a(a), _b(b), _c(c) {}
E e() const;
private:
A _a;
B& _b;
C* _c;
}; // class MyClass
} // namespace project
This header pulls in 5 other headers, but how many are actually necessary ?
a.hpp
is necessary, because _a
of type A
is an attribute of the classb.hpp
is not necessary, we only have a reference to B
c.hpp
is not necessary, we only have a pointer to C
d.hpp
is necessary, we call methods on D
e.hpp
is not necessary, it only appears as a returnOK, let's clean this up!
#include "project/a.hpp" // defines class A
#include "project/d.hpp" // defines class D
namespace project { class B; }
namespace project { class C; }
namespace project { class E; }
namespace project {
class MyClass {
public:
explicit MyClass(D const& d): _a(d.a()), _b(d.b()), _c(d.c()) {}
MyClass(A a, B& b, C* c): _a(a), _b(b), _c(c) {}
E e() const;
private:
A _a;
B& _b;
C* _c;
}; // class MyClass
} // namespace project
Can we do better ?
Well, first we can see that we call methods on D
only in the constructor of the class, if we move the definition of D
out of the header, and put it in a .cpp
file, then we won't need to include d.hpp
any longer!
// no need to illustrate right now ;)
But... what of A
?
It is possible to "cheat", by remarking that merely holding a pointer does not requires a full definition. This is known as the Pointer To Implementation idiom (pimpl for short). It trades off run time for lighter dependencies, and adds some complexity to the class. Here is a demo:
#include <memory> // don't really worry about std headers,
// they are pulled in at one time or another anyway
namespace project { class A; }
namespace project { class B; }
namespace project { class C; }
namespace project { class D; }
namespace project { class E; }
namespace project {
class MyClass {
public:
explicit MyClass(D const& d);
MyClass(A a, B& b, C* c);
~MyClass(); // required to be in the source file now
// because for deleting Impl,
// the std::unique_ptr needs its definition
E e() const;
private:
struct Impl;
std::unique_ptr<Impl> _impl;
}; // class MyClass
} // namespace project
And the corresponding source file, since that were the interesting things occur:
#include "project/myClass.hpp" // good practice to have the header included first
// as it asserts the header is free-standing
#include "project/a.hpp"
#include "project/b.hpp"
#include "project/c.hpp"
#include "project/d.hpp"
#include "project/e.hpp"
struct MyClass::Impl {
Impl(A a, B& b, C* c): _a(a), _b(b), _c(c) {}
A _a;
B& _b;
C* _c;
};
MyClass::MyClass(D const& d): _impl(new Impl(d.a(), d.b(), d.c())) {}
MyClass::MyClass(A a, B& b, C* c): _impl(new Impl(a, b, c)) {}
MyClass::~MyClass() {} // nothing to do here, it'll be automatic
E MyClass::e() { /* ... */ }
Okay, so that was the low and gritty. Further reading:
a.b().c().d()
), it means you have leaky abstraction, and forces you the include the whole world to do anything. Instead, you should be calling a.bcd()
which hides the details from you.There are many ways to encapsulate and hide information, your quest just begins!