The idea is this: I have SportsCar derived from Car, a Car consists of an Engine, each time Car.drive() is called, it calls Engine.consumeGas(), which in turn creates a Gas object and calls Gas.burn(). As you can see in the source code below.
Each class has its own header file and cpp file. And I wrote a Makefile (for Microsoft's NMAKE program).
The dependency is like this:
test.exe: main.obj Car.obj SportsCar.obj Engine.obj Gas.obj
main.obj: main.cpp Car.h SportsCar.h
Car.obj: Car.cpp Car.h
SportsCar.obj: SportsCar.cpp SportsCar.h // Here is what went wrong
Engine.obj: Engine.cpp Engine.h Gas.h
Gas.obj: Gas.cpp Gas.h
Build the program and run test.exe, produced the output:
Car drive
Engine consuming gas
Gas burning
SportsCar drive
Engine consuming gas
Gas burning
The problem is if I delete the member engine from Car and delete the call engine.consumeGas() in Car.drive() and rebuild the whole program, only Car.cpp and main.cpp are recompiled (SportsCar.cpp is not) and the linker won't complain at all.
After rebuilding, run test.exe get the output:
Car drive
SportsCar drive
Engine consuming gas
Gas burning
Apparently this result has completely violated the semantics of C++.
If I write the dependency like this, the problem will be fixed.
SportsCar.obj: SportsCar.cpp SportsCar.h Car.h
So if I have one source file A which includes a header file B, which in turn includes another header file C, and in A, the class C is used, I have to say A depends on both B and C, not just A depends on B?
If the project is large enough, I'm afraid I'll get lost trying to find out which file depends on which file while writing a Makefile.
Code:
main.cpp
#include "Car.h"
#include "SportsCar.h"
int main()
{
Car *car = new Car();
car->drive();
delete car;
car = new SportsCar();
car->drive();
delete car;
return 0;
}
Car.h
#ifndef CAR_H
#define CAR_H
#include "Engine.h"
class Car
{
protected:
Engine engine;
public:
virtual void drive();
virtual ~Car();
};
#endif // CAR_H
Car.cpp
#include "Car.h"
#include <iostream>
using namespace std;
void Car::drive()
{
cout << "Car drive" << endl;
engine.consumeGas();
}
Car::~Car()
{
// do nothing
}
SportsCar.h
#ifndef SPORTSCAR_H
#define SPORTSCAR_H
#include "Car.h"
class SportsCar : public Car
{
public:
void drive();
};
#endif // SPORTSCAR_H
SportsCar.cpp
#include "SportsCar.h"
#include <iostream>
using namespace std;
void SportsCar::drive()
{
cout << "SportsCar drive" << endl;
engine.consumeGas();
}
Engine.h
#ifndef ENGINE_H
#define ENGINE_H
class Engine
{
public:
void consumeGas();
};
#endif // ENGINE_H
Engine.cpp
#include "Engine.h"
#include "Gas.h"
#include <iostream>
using namespace std;
void Engine::consumeGas()
{
cout << "Engine consuming gas" << endl;
Gas g;
g.burn();
}
Gas.h
#ifndef GAS_H
#define GAS_H
class Gas
{
public:
void burn();
};
#endif // GAS_H
Gas.cpp
#include "Gas.h"
#include <iostream>
using namespace std;
void Gas::burn()
{
cout << "Gas burning" << endl;
}
1 Answer 1
You should generally never hardcode dependencies into makefiles. Instead, you should use the -M -MF flags to generate the dependencies during compilation and include the resulting file into your Makefile. Otherwise your dependencies will always be out of sync with reality.
Sadly, automatic dependency generation is a complex topic which I can't explain here in full detail. Many of the details can be found in this article: Auto-Dependency Generation
SportsCar.cppdoesn't have a#include "Car.h", but it still has dependency onCar.h.-MMoption family.