This web page shows how to structure your code to make it more modular and more reusable. The example code here implements the car/engine example we've discussed in class.
I start out with all the code in a single file -- clearly, not a very modular design. In the second version I break out the car and engine classes, but still have all of their code in header files. The final version is the most modular: the car and engine code is divided into interface (header) and implement (cpp). This is how I would like you to structure your code.
Here I put all of the code in a single cpp file: main.cpp.
There are several problems with this approach. I have to compile all the code every time I change anything. It would also be very hard for multiple people to work on the code, since we'd all be editing the same file. Finally, there is not a clean separation between the interfaces and implementations of the classes.
In this version I separated the car and engine classes from the main, but
their interfaces and implementations are still combined in a single header
file. Notice that main.cpp must include the two header files.
This version is much more modular, but still doesn't separate interface from implementation. I could not, for example, send someone the interface for the engine without also sending them the implementation (which might be secret!).
See the code here.
The final version shows the most modular design: separate headers for each
class, each with its own separate cpp file for the implementation. Again, note
how the files use #include to get the interfaces they need.
See the code here.
Even for small projects it is often desirable to automate the process of compiling the code. You can automate this build process by writing a compile script or by designing a Makefile. The Makefile approach is more efficient, but also trickier to get right.
clang++ -c engine.cpp clang++ -c car.cpp clang++ -c main.cpp clang++ engine.o car.o main.o -o simulator
You write this program just like any other and store it in a file (call
it compile). You "run" this program (to build your C++ program)
using the shell interpreter:
lab116a% bash compile lab116a% ls simulator simulator lab116a% ./simulator Speed is 0 Speed is 12 Speed is 36
You can also tell the system to treat "compile" as a program that you can run directly. Add the name of the interpreter to the top of the compile script:
#!/bin/bash clang++ -c engine.cpp clang++ -c car.cpp clang++ -c main.cpp clang++ engine.o car.o main.o -o simulator
To run the script:
lab116a% ./compile lab116a% ls simulator simulator
A Makefile is another kind of interpreted programming language, which you run using the "make" command. The main difference between a compile script and a Makefile is that a Makefile describes the specific dependences between the files, allowing make to selectively compile only the parts of the program affected by changes.
Each entry of a Makefile describes (a) how a file depends on other files, and (b) how to rebuild that file if it becomes out-of-date:
afile: otherfile yetanotherfile
Commands to rebuild afile from otherfile and yetanotherfile
Make looks at the timestamp (the date/time of the most recent modifications) of each file on the right side of the colon. If any of them are newer than the file on the left, make runs the commands.
WARNING 1: The command part must have a tab character before it (not a sequence of spaces), or the Makefile will not work properly!
For our projects we will be dealing with two kinds of dependences: object files (.o files) depend on cpp and header file, and programs depend on object files. The key idea to tell make what it needs to do when parts of the source change. So, for example, what cpp files need to be recompiled if a particular header file changes?
Here is a complete Makefile for the example above:
simulator: main.o car.o engine.o
clang++ main.o car.o engine.o -o simulator
main.o: main.cpp car.h engine.h
clang++ -c main.cpp
car.o: car.cpp car.h engine.h
clang++ -c car.cpp
engine.o: engine.cpp engine.h
clang++ -c engine.cpp
WARNING 2: It is completely up to you to make sure that the Makefile you write properly represents the dependences between the files. If you lie to make, it will not build your code correctly!
What's nice about make is that it does only as much work as is necessary. For
example, if I only modify car.h, it can figure out that engine.cpp
does not need to be recompiled.
lab116a% make
clang++ -c main.cpp
clang++ -c car.cpp
clang++ main.o car.o engine.o -o simulator
Back to Comp15.