Show Lecture.SeparateCompilation as a slide show.
CS253 Separate Compilation
Separation, by Edvard Munch
Separate Compilation
It’s often useful to separate the class interface (*.h
)
from the class implementation (*.cc
).
Example
Foo.h
| Foo.cc
|
---|
#ifndef FOO_H_INCLUDED
#define FOO_H_INCLUDED
#include <iostream>
class Foo {
public:
Foo();
int get_data() const;
private:
double data;
};
std::ostream & operator<<(std::ostream &,
const Foo &);
#endif /* FOO_H_INCLUDED */
|
#include "Foo.h"
using namespace std;
Foo::Foo() : data(42.0) {
}
int Foo::get_data() const {
return data;
}
ostream &operator<<(ostream &os,
const Foo &f) {
return os << f.get_data();
}
|
Contents
The *.h
file only contains the method declarations (signatures),
function declarations (for non-member functions & operators) and
declarations of data members. The *.cc
file contains the actual
code for the methods and functions. Neither one contains main()
.
Why?
- Consider the
*.h
file as containing the interface,
the contract between you, the developer, and the end user.
We can develop the interface long before we have to
actually write the code. It’s easier to consider the merits
of the interface unencumbered by the details of the implementation.
- The users of your class are only interested in the interface,
not the implementation. They don’t need the
*.cc
.
- This enables the class to be compiled separately. For example,
if the class is part of a product that you sell, you can deliver
only
Foo.h
and Foo.o
to your customers. This keeps your
implementation source code out of your customer’s hands.
Typing
“But it’s more typing!”
If you’re afraid of typing, then you have certainly chosen the wrong
profession, and the wrong century to live in.
Compilation
Compile the code like this:
g++ -Wall main.cc Foo.cc
If you do this with a Makefile
, make sure to have main.cc
also
depend upon Foo.h
. After all, if you change Foo.h
(say, by
adding a member variable) then you must recompile main.cc
, because
the size of Foo
has changed.
Beware
Do not compile the *.h
file, unless you really know what you’re
doing. That will result in a *.gch
(compiled header) file, which will
cause no end of trouble. Remove it if you ever find it.
Example
Consider this simple program. It gets π from a header file:
% cat main.cc
#include "pi.h"
#include <iostream>
int main() {
std::cout << pi << '\n';
return 0;
}
% cat pi.h
constexpr auto pi = 3.14;
% g++ -Wall main.cc
% ./a.out
3.14
% ls
a.out main.cc pi.h
Note that pi.h
is not mentioned in the compile command.
What if we try to compile pi.h
?
% g++ -Wall main.cc pi.h
% ls
a.out main.cc pi.h pi.h.gch
% ./a.out
3.14
Let’s improve the value of π:
% echo "constexpr auto pi = 3.14159;" >pi.h
% g++ -Wall main.cc
% ./a.out
3.14
Hey, why didn’t it work?
% rm pi.h.gch
% g++ -Wall main.cc
% ./a.out
3.14159
Moral of the story
- When the compiler sees
#include
"pi.h"
, it first looks for
pi.h.gch
, and uses that precompiled header file if it’s present.
- This means that, if you change
pi.h
, it doesn’t matter,
because the compiler uses pi.h.gch
if it’s there. Ack!
- Same principle as having
foo.cc
and foo.o
.
- This can be a powerful tool, if used properly with a well-written
Makefile
.
- My attitude: Compiled headers ain’t worth it. Stay away from them.
Write a
Makefile
so you won’t screw up your compile comand.