I have a base class here:
typedef float Pounds;
typedef int Seconds;
typedef float MilliJoulesPerLiter;
typedef float MilliJoules;
typedef int Liters;
class Fuel {
protected:
static const Pounds carbonDioxideEmmissionsPerLiter;
static const Seconds burnTime;
static const MilliJoulesPerLiter energyYield;
Liters volume {10};
MilliJoules totalMilliJoules {0};
public:
Fuel() : Fuel {10} {};
Fuel(int vol) : volume(vol) {
totalMilliJoules = volume * energyYield;
}
virtual void burn() {
cout << "Burning fuel..." << endl;
int quantity = volume < burnTime ? volume : burnTime;
Sleep(quantity);
volume -= quantity;
totalMilliJoules /= volume * energyYield;
totalEmmissions += carbonDioxideEmmissionsPerLiter * quantity;
cout << "Done burning fuel. Total millijoules burnt: " << quantity * energyYield << endl;
}
};
As you can see, I have a burn()
method that uses variables that the base class itself defines. That burn()
method should work the same exact way for all subclasses, the only difference is that the subclasses will have different values for those members.
So, here are my subclasses:
class Gasoline : public Fuel {
protected:
static const Pounds carbonDioxideEmmissionsPerLiter {5.07};
static const Seconds burnTime {10};
static const MilliJoulesPerLiter energyYield {34.8};
Liters volume {0};
MilliJoules totalMilliJoules {0};
public:
Gasoline() : Fuel {} {};
Gasoline(int vol) : Fuel {vol} {};
};
class Coal : public Fuel {
protected:
static const Pounds carbonDioxideEmmissionsPerLiter {2.42};
static const Seconds burnTime {5};
static const MilliJoulesPerLiter energyYield {23.9};
Liters volume {0};
MilliJoules totalMilliJoules {0};
public:
Coal() : Fuel {} {};
Coal(int vol) : Fuel {vol} {};
};
class Propane : public Fuel {
protected:
static const Pounds carbonDioxideEmmissionsPerLiter {3.17};
static const Seconds burnTime {20};
static const MilliJoulesPerLiter energyYield {25};
Liters volume {0};
MilliJoules totalMilliJoules {0};
public:
Propane() : Fuel {} {};
Propane(int vol) : Fuel {vol} {};
};
Every time I make a derived class object out of these and I call burn()
, I get the base class values. I do know exactly why this is happening; obviously if I don't explicitly override the method burn()
, then it will default to the base class version.
However, burn()
is of rather nontrivial length, and so copy/pasting it throughout all the subclasses seems very repetitive.
Is there a way to write a method only once in a base class, where the ONLY difference is what the values of the class attributes are, and it uses those?
Essentially, we use virtual
functions to resolve what version of the function we use at runtime, for virtual function calls. Are there virtual member accesses too?
burn()
function virtual
, even though I know that's only for functions, but it was worth a try. Still uses base class version without an override.this->
, to no avail.virtual
keyword to member attributes, which is not valid syntax.burn()
.burn()
You can't override a base class's static
members in derived classes, C++ simply does not work that way.
Your base class data members should not be static
to begin with. Make them non-static instead, so that each object instance carries its own values. And then either:
class Fuel {
protected:
const Pounds carbonDioxideEmmissionsPerLiter;
const Seconds burnTime;
const MilliJoulesPerLiter energyYield;
Liters volume {10};
MilliJoules totalMilliJoules {0};
public:
Fuel(Pounds carbonDioxideEmmissionsPerLiter, Seconds burnTime, const MilliJoulesPerLiter energyYield, Liters volume = 10) :
carbonDioxideEmmissionsPerLiter(carbonDioxideEmmissionsPerLiter),
burnTime(burnTime),
energyYield(energyYield),
volume(volume)
{
totalMilliJoules = volume * energyYield;
}
void burn() {
cout << "Burning fuel..." << endl;
int quantity = volume < burnTime ? volume : burnTime;
Sleep(quantity);
volume -= quantity;
totalMilliJoules /= volume * energyYield;
totalEmmissions += carbonDioxideEmmissionsPerLiter * quantity;
cout << "Done burning fuel. Total millijoules burnt: " << quantity * energyYield << endl;
}
};
class Gasoline : public Fuel {
public:
Gasoline(Liters volume = 10) : Fuel(5.07, 10, 34.8, volume) {}
};
class Coal : public Fuel {
public:
Coal(Liters volume = 10) : Fuel(2.42, 5, 23.9, volume} {}
};
class Propane : public Fuel {
public:
Propane(Liters volume = 10) : Fuel(3.17, 20, 25, volume) {}
};
virtual
getter/setter methods in the base class to access the values, and then have the derived classes override those methods, eg:class Fuel {
protected:
virtual Pounds get_carbonDioxideEmmissionsPerLiter() const = 0;
virtual Seconds get_burnTime() = 0;
virtual MilliJoulesPerLiter get_energyYield() = 0;
Liters volume {10};
public:
Fuel(Liters volume = 10) : volume(volume) {}
// can't calculate this value in the constructor anymore,
// since a base class constructor can't call virtual methods
// on a derived class...
MilliJoules get_totalMilliJoules() const { return volume * get_energyYield(); }
void burn() {
cout << "Burning fuel..." << endl;
int quantity = volume < get_burnTime() ? volume : get_burnTime();
Sleep(quantity);
volume -= quantity;
totalEmmissions += get_carbonDioxideEmmissionsPerLiter() * quantity;
cout << "Done burning fuel. Total millijoules burnt: " << quantity * get_energyYield() << endl;
}
};
class Gasoline : public Fuel {
protected:
Pounds get_carbonDioxideEmmissionsPerLiter() const override { return 5.07; }
Seconds get_burnTime() const override { return 10; }
MilliJoulesPerLiter get_energyYield() const override { return 34.8; }
public:
Gasoline(Liters volume = 10) : Fuel(volume) {}
};
class Coal : public Fuel {
protected:
Pounds get_carbonDioxideEmmissionsPerLiter() const override { return 2.42; }
Seconds get_burnTime() const override { return 5; }
MilliJoulesPerLiter get_energyYield() const override { return 23.9; }
public:
Coal(Liters volume = 10) : Fuel(volume) {}
};
class Propane : public Fuel {
protected:
Pounds get_carbonDioxideEmmissionsPerLiter() const override { return 3.17; }
Seconds get_burnTime() const override { return 20; }
MilliJoulesPerLiter get_energyYield() const override { return 25; }
public:
Propane(Liters volume = 10) : Fuel(volume) {}
};
On the other hand, if you really want to make use of static
data members in the derived classes, you can use CRTP (Curiously Recurring Template Pattern), eg:
class Fuel {
protected:
Liters volume {10};
MilliJoules totalMilliJoules {0};
public:
Fuel(Liters volume = 10) : volume(volume) {}
virtual void burn() = 0;
};
template<typename FuelType>
class FuelT : public Fuel {
public:
FuelT(Liters volume = 10) : Fuel(volume) {
totalMilliJoules = volume * FuelType::energyYield;
}
void burn() override {
cout << "Burning fuel..." << endl;
int quantity = volume < FuelType::burnTime ? volume : FuelType::burnTime;
Sleep(quantity);
volume -= quantity;
totalMilliJoules /= volume * FuelType::energyYield;
totalEmmissions += FuelType::carbonDioxideEmmissionsPerLiter * quantity;
cout << "Done burning fuel. Total millijoules burnt: " << quantity * FuelType::energyYield << endl;
}
};
class Gasoline : public FuelT<Gasoline> {
public:
static const Pounds carbonDioxideEmmissionsPerLiter {5.07};
static const Seconds burnTime {10};
static const MilliJoulesPerLiter energyYield {34.8};
Gasoline(Liters volume = 10) : FuelT<Gasoline>(volume) {}
};
class Coal : public FuelT<Coal> {
public:
static const Pounds carbonDioxideEmmissionsPerLiter {2.42};
static const Seconds burnTime {5};
static const MilliJoulesPerLiter energyYield {23.9};
Coal(Liters volume = 10) : FuelT<Coal>(volume) {}
};
class Propane : public FuelT<Propane> {
public:
static const Pounds carbonDioxideEmmissionsPerLiter {3.17};
static const Seconds burnTime {20};
static const MilliJoulesPerLiter energyYield {25};
Propane(Liters volume = 10) : FuelT<Propane>(volume) {}
};