Problem overview
You write three classes. Shipping is the standard option and the parent. Express and Economy inherit from it, and each one replaces both of its methods with its own versions. Every option answers the same two questions: name() returns its name, and cost(weight) returns the price of a parcel of that many kilograms.
| Class | name() | cost(weight) |
|---|---|---|
Shipping | "Standard" | 5 + weight |
Express | "Express" | 10 + 2 * weight |
Economy | "Economy" | 3 + weight / 2 |
For a parcel of 4 kilograms the three options give 9, 18 and 5. The lesson's tests call the same two methods on every option and print name: cost, so each class has to give its own answer when called through a pointer to Shipping.
A second example, with a weight of 7: Standard costs 5 + 7 = 12, Express costs 10 + 2 * 7 = 24, and Economy costs 3 + 7 / 2. In whole-number division 7 / 2 is 3, so Economy costs 6. Economy is the only option where the fraction matters, and the statement asks for it to be rounded down.
Approach: one parent, three versions of each method
The parent defines what every shipping option can do. A child class inherits that and may override a method: write a method with the same name and parameters that replaces the parent's version for objects of the child class.
Two keywords make this work in C++:
virtualin the parent says that a call may need to run a child's version. The call is decided by the real class of the object at run time, not by the type of the pointer.overridein the child asks the compiler to check that the method really overrides a virtual method of the parent.
The starter code already has both keywords in place. Your job is to write the bodies.
Think of what happens at the call s->cost(4) when s has type Shipping*. The compiler only knows that s points to some Shipping. Because cost is virtual, the program looks at the object itself when the call runs. If the object is an Economy, Economy::cost runs and returns 3 + 4 / 2, which is 5. If it is a plain Shipping, the parent's version runs and returns 9. This is called polymorphism: one call, many behaviours, chosen by the object.
Step by step
- Write
Shipping::name: return"Standard". - Write
Shipping::cost: a base price of 5 plus 1 per kilogram, so5 + weight. - Write
Express::name: return"Express". - Write
Express::cost:10 + 2 * weight. Without this method an express parcel would get the parent's price. - Write
Economy::name: return"Economy". - Write
Economy::cost:3 + weight / 2.
Each class overrides only for itself. Changing Express has no effect on Economy or on Shipping.
The solution in C++
class Shipping {
public:
virtual string name() {
return "Standard";
}
virtual int cost(int weight) {
return 5 + weight;
}
};
class Express : public Shipping {
public:
string name() override {
return "Express";
}
int cost(int weight) override {
return 10 + 2 * weight;
}
};
class Economy : public Shipping {
public:
string name() override {
return "Economy";
}
int cost(int weight) override {
return 3 + weight / 2;
}
};
Using the classes
Because all three classes share the type Shipping, one pointer type can hold any of them, and the same two calls work on each:
Shipping standard;
Express express;
Economy economy;
Shipping* options[] = {&standard, &express, &economy};
for (Shipping* s : options) {
cout << s->name() << ": " << s->cost(7) << "\n";
}
This prints:
Standard: 12
Express: 24
Economy: 6
The loop never asks which class it holds. A fourth option, say Overnight, would work in the same loop without changing it.
Details that matter
public:comes first in each class because members of aclassare private by default.: public ShippingmakesExpressandEconomychildren ofShippingwhose public methods stay public.- Pointers call methods with
->, and objects with a dot. In the loop abovesis a pointer, so it iss->cost(7). - If you create options with
newand delete them through aShipping*, the parent should also have a virtual destructor, writtenvirtual ~Shipping() = default;. The lesson's tests don't need it, and the starter code leaves it out here. weight / 2divides twoints, so the fraction is dropped: with a weight of 7 it gives 3, and the cost is 6. Writing(3 + weight) / 2instead would give 5 for the same weight, because the 3 would be halved too.
Edge cases and common mistakes
- Forgetting
virtualin the parent. The code still compiles, but a call through aShipping*then runs the parent's method. With non-virtualnameandcostinShipping, anExpressobject used through aShipping*printsStandard: 9for a weight of 4, while the same object used directly printsExpress: 18. - A header that doesn't match. If the child's method differs from the parent's, even by a
constafter the parameter list,overrideturns the mistake into a compiler error:marked 'override', but does not override. Withoutoverridethe mismatch would silently create a second, unrelated method. - Weight 0. Only the base prices are left: 5, 10 and 3.
- Odd weights in Economy. Weights 1 and 7 give 3 and 6, because the half is rounded down.
Testing
Try these weights with all three options, in the order Standard, Express, Economy:
| Weight | Standard | Express | Economy |
|---|---|---|---|
| 4 | 9 | 18 | 5 |
| 0 | 5 | 10 | 3 |
| 7 | 12 | 24 | 6 |
| 1 | 6 | 12 | 3 |
Try it in the editor above.
