Practice Lab 3 — Rectangle and geometry model¶
Difficulty: Beginner · Estimated time: 2–3 hours · Module 2: Classes and Objects
Your first class. Small enough to hold in your head, and it still manages to contain a decision that every larger class repeats: what is state, and what is computed?
At a glance¶
| Difficulty | Beginner |
| Estimated time | 2–3 hours |
| Module | Module 2 — Classes and Objects |
| Concepts practised | Class syntax, public operations, data members, object interaction, multiple objects, computed versus stored values, const member functions |
| Prerequisite | Practice Lab 2 |
| Class design | given — two classes, named for you |
Scenario¶
A workshop cuts rectangular panels from sheet material. A quote has to say the panel's area, its perimeter, whether it fits inside a standard sheet, and what it costs — and the price list changes often enough that the workshop wants the arithmetic in one place.
You are given the two objects the workshop talks about: a Rectangle and a Sheet.
Problem statement¶
Implement Rectangle with a width and a height, and Sheet with a width, a height and a price per square
metre. Both in one source file.
Operations:
Rectangle
- Construct from a width and a height.
area()andperimeter().is_square()— true when the sides are equal.fits_inside(width, height)— true when the rectangle fits inside a given shape, allowing rotation (a 2×6 panel fits inside a 6×3 sheet).scaled(factor)— a new rectangle with both sides multiplied.describe()— a one-line description for the quote.
Sheet
- Construct from a width, a height and a price per square metre.
area()andcost_of(const Rectangle&)— the material cost of cutting that panel from the sheet.fit_count(const Rectangle&)— how many panels fit in a simple grid layout, ignoring rotation.describe().
A main that quotes three panels and prints the results.
Learning objectives¶
By the end of this lab you will be able to:
- Write a class with data members and member functions, and construct objects with a chosen initial state.
- Decide for each piece of information whether it is stored or computed, and state the reason.
- Mark the member functions that do not change the object
const, and explain what would break without it. - Pass one object to another object's operation without copying it, and say who owns what.
- Explain, in one sentence, which class owns the rule "a panel fits inside a sheet".
Functional requirements¶
- A rectangle with a non-positive width or height is rejected at construction with a message on
std::coutand an object that reports itself as invalid — or by throwing, if you prefer; choose one and use it consistently for both classes. areaandperimeterare correct for whole numbers and for fractional sides.is_squareis true when the difference between the sides is zero.fits_insidereturns true when the rectangle fits with or without rotation.scaledreturns a new rectangle and does not modify the original — demonstrate this in the output.cost_ofuses the sheet's price and the rectangle's area; it rounds the cost up to whole pence.fit_countis zero when the panel does not fit, and the whole-sheet grid count otherwise.describefor both classes is a single line, and the two are distinguishable in the output.
Constraints¶
- One source file;
g++ -std=c++17 -Wall -Wextra -Werror. - No public data members. The sides are private, with accessors where access is genuinely needed.
- Every operation that does not change the object is
const. The compiler will enforce that you meant it: call a non-constoperation on aconstobject and read the error. - No
friend, no inheritance, nostd::function. - Money in whole pence as an integer; geometry in
double. - No warnings.
Starter task¶
Before writing the classes, answer in design-notes.md:
- Which of these can be computed from the others: width, height, area, perimeter, whether it is a square? Which will you store, and why?
fit_counthas a rule — "ignore rotation, whole panels only, a simple grid". Where does that rule belong: inSheet, inRectangle, or inmain? Write the sentence that decides it.- A
Rectangleof width 3 and height 4, and one of width 4 and height 3, have the same area. Are they equal? Wouldoperator==be a good idea here, and what would it have to mean?
Then write the class declarations with no bodies, and check that no data member is public.
Implementation tasks¶
- Implement
Rectangle, with the four computed operations andscaled. - Implement
Sheet, usingRectangleby reference incost_ofandfit_count. - Write
main, quoting three panels: one square, one tall, one that does not fit. - Add a
const Rectangleobject tomainand callareaon it. Then removeconstfromareaand read the compiler error, and paste it intodesign-notes.md. - Run every test case and record the output.
Test cases¶
| # | Action | Expected result |
|---|---|---|
| 1 | Rectangle(3, 4) |
area 12, perimeter 14, not a square |
| 2 | Rectangle(5, 5) |
area 25, is a square |
| 3 | Rectangle(2, 6).fits_inside(6, 3) |
true — it fits rotated |
| 4 | Rectangle(7, 2).fits_inside(6, 3) |
false |
| 5 | Rectangle(3, 4).scaled(2) |
a new 6×8; the original is still 3×4 |
| 6 | Sheet(120, 240, 1500).cost_of(Rectangle(30, 40)) |
the area in square metres × 1500 pence, rounded up |
| 7 | fit_count for a panel larger than the sheet |
0 |
| 8 | Sheet(120, 240, 1500).fit_count(Rectangle(30, 40)) |
a whole-sheet grid count you can check by hand |
| 9 | Rectangle(0, 4) or Rectangle(-1, 2) |
rejected at construction, with a message |
| 10 | Calling area() on a const Rectangle |
compiles; the same call fails to compile if area is not const |
Boundary cases to run deliberately: a rectangle exactly as large as the sheet; a rectangle of width equal to height; fractional sides such as 2.5 × 4.
Expected behaviour¶
$ ./panels
Quote for 3 panels
panel 1: 30 x 40 cm, area 1200.0 cm2, perimeter 140.0 cm [not square]
panel 2: 50 x 50 cm, area 2500.0 cm2, perimeter 200.0 cm [square]
panel 3: 200 x 10 cm, area 2000.0 cm2, perimeter 420.0 cm [not square]
sheet: 120 x 240 cm, 2.88 m2, 1500 pence/m2
panel 1: cost 18 pence, fits 24 per sheet
panel 2: cost 38 pence, fits 4 per sheet
panel 3: cost 30 pence, fits 0 per sheet - does not fit
panel 1 scaled by 2 is 60 x 80 cm, and the original is still 30 x 40 cm
a const panel 1 reports area 1200.0 cm2
Extension challenge¶
Extend the sheet to support rotation in fit_count, so a panel that only fits turned is counted.
Then write, in design-notes.md:
- How the count changed, with the example that proves it.
- Why "fits with rotation" and "counts with rotation" are different questions, even though both are about the same two rectangles.
- The operation you would add next for a real cutting plan, and what would make it hard.
Debugging challenge¶
The workshop reports that some panels are charged for and then rejected:
double cost_of(Rectangle panel) const {
if (panel.area() <= area()) { // fits?
return price_per_m2_ * panel.area() / 10000.0;
}
return price_per_m2_ * area() / 10000.0; // charge for the whole sheet instead
}
Reproduce it with a panel whose area is smaller than the sheet's but whose sides are longer, and answer:
- Which question did the fallback branch answer, and which question did it think it was answering?
- Why is "compare the areas" not the same test as "does it fit"?
- What is the smallest change that makes the operation honest about a panel that does not fit?
Reflection questions¶
- Which of your operations could have been a free function instead of a member? Which could not?
fit_countlives onSheet. What would have to be true for it to belong toRectangleinstead?- What does
conston a member function change about the objects you are allowed to call it on, and why does that matter for a container of sheets later? - Two of your operations round:
cost_ofand the lengths. Where should rounding happen, and how many places did you have to decide it in? - If the sheet's price changed, which objects would need updating, and which operations read the price?
- Which single change would make your two classes unusable by a caller — a private constructor, an
operation no longer
const, or a data member made public? Answer with what breaks inmain.
Completion checklist¶
- [ ] It compiles with
-Wall -Wextra -Werrorand no warnings - [ ] No public data members in either class
- [ ] Every operation that does not change the object is
const, and theconstobject inmainproves it - [ ]
scaledreturns a new rectangle and leaves the original untouched — shown in the output - [ ] All ten test cases run, with output recorded
- [ ] Boundary cases run, including a panel exactly the size of the sheet
- [ ]
design-notes.mdstates which values are stored and which are computed, with reasons - [ ] The compiler error from removing
constis pasted intodesign-notes.md, verbatim - [ ] I can say which class owns the "does it fit" rule, in one sentence
- [ ] I can name one operation I chose not to write, and why it was not needed
Related: Lab index · Module 2 — Classes and Objects · Previous: Practice Lab 2 · Next: Practice Lab 4 — Bank account as a type