Skip to content

Mini Project 5 — Shape Areas

Difficulty: Intermediate · Estimated effort: 4 hours · Prerequisites: Module 6 — Inheritance, Module 7 — Polymorphism, Module 8 — Abstract Classes · Combines: an abstract interface, virtual functions, polymorphic containers, unique_ptr

No complete solution is provided. The class list below is a starting point for your design, not a design.


1. Scenario

A sign-maker quotes jobs by wall area. A job is a list of panels — rectangles, circles and right triangles — and the quote is the total area plus the number of panels. The estimator currently copies each panel's dimensions into a spreadsheet and applies a formula chosen from a dropdown, which is exactly what polymorphism is for.


2. Problem statement

Build a small geometry library with an abstract Shape interface and concrete shapes implementing it, so that a new shape can be added without editing the code that totals areas or prints a report.


3. User requirements

  • [ ] Add panels of several kinds to one job.
  • [ ] Get the total area of the job and the number of panels.
  • [ ] Print a report listing each panel with its own area.
  • [ ] Be told when a shape's parameters are invalid rather than getting a nonsense area.
  • [ ] Add a new shape later without touching the report or the total.

4. Functional requirements

  • [ ] Abstract Shape with area() and name() pure virtual, plus operator<< or describe().
  • [ ] Concrete Rectangle, Circle, Triangle.
  • [ ] A job owning a std::vector<std::unique_ptr<Shape>>.
  • [ ] Adding a panel takes ownership; the container destroys panels when it dies.
  • [ ] A total-area function that takes the container and calls only the interface.
  • [ ] Invalid dimensions (negative length, non-positive radius or height) refused.

5. Non-functional requirements

  • [ ] A polymorphic base must have a virtual destructor; demonstrate that it does.
  • [ ] No slicing anywhere: panels are held and passed by reference or smart pointer.
  • [ ] No new/delete in your code — construction goes through std::make_unique.
  • [ ] Builds warning-free with g++ -std=c++17 -Wall -Wextra -Werror.
  • [ ] The area computations use a named constant for pi, defined once.

6. Suggested features

  • Scale every panel in a job by a factor, returning a new job.
  • Sort the report by area, largest first.
  • A Square implemented as a rectangle and a written note on whether that was wise.
  • Perimeter as a second interface function, with the shapes that cannot implement it questioned.

7. OOP concepts involved

Concept Where it appears
Inheritance Each shape is a shape, and only for that reason
Abstract classes Shape promises area(); the compiler enforces the promise
Polymorphism The total and the report see one interface, many implementations
Virtual destructor Deleting a panel through Shape& must run the derived destructor
Composition / ownership The job owns its panels, via unique_ptr
Object slicing Why panels are never stored by value — reproduce it, then avoid it

8. Suggested design considerations

Ask whether inheritance is the right tool at all. A Circle and a Rectangle share an interface, not an implementation. That is exactly the case for a pure interface — and it is why there should be no data members in Shape.

Reproduce slicing on purpose. Put a Circle into a std::vector<Shape> (or pass one by value) and watch what happens. Then write one sentence explaining the result. This is the single most valuable minute of the project.

Keep pi in one place. A constexpr double pi in one header, or std::numbers::pi if your toolchain provides it. Two pi constants that differ in the eighth digit is a real bug class.

Decide what a total costs. Your total loops over panels. Note what that costs if a job is later queried a thousand times, and whether caching belongs in the design or in the caller.


9. Minimum viable version

Shape with a virtual destructor and two pure virtuals; three concrete shapes; a job owning a vector<unique_ptr<Shape>>; a total and a report; a main printing both.


10. Intermediate extension

Add a fourth shape without editing the total or the report, and count how many files you touched. Then add an invalid-dimension case to a shape and verify the refusal path is tested, not just written.


11. Advanced extension

Add job scaling and report sorting through the interface only. Then introduce a ShapeFactory that turns a small text description ("rect 3 4") into a panel, and answer in writing whether the factory removed or merely relocated the conditional logic that names each shape.


12. Testing checklist

  • [ ] A rectangle of 3 × 4 reports area 12
  • [ ] A circle of radius 1 reports area within tolerance of pi
  • [ ] A triangle's area is a half base times height, checked at a boundary
  • [ ] The total equals the sum of the individual areas printed in the report
  • [ ] An empty job reports 0 and 0 panels
  • [ ] A negative dimension is refused with a message
  • [ ] Destroying the job destroys every panel — verified with a destruction counter
  • [ ] delete through a Shape* runs the derived destructor — verified the same way

13. Documentation checklist

  • [ ] A one-line responsibility for each class
  • [ ] Why Shape has a virtual destructor, stated where a reader will see it
  • [ ] The invariants of each concrete shape
  • [ ] The slicing experiment written up: what happened, and one sentence of why
  • [ ] Where pi lives and why there is only one

14. Self-evaluation rubric

Criterion Excellent looks like Weight
Interface design Shape is an interface with no state; adding a shape needs no edits elsewhere 25%
Correctness Areas verified including boundaries; empty job handled 25%
Lifetime safety unique_ptr throughout; virtual destructor demonstrated; no slicing 25%
Failure handling Invalid dimensions refused and tested 10%
Documentation Responsibilities, invariants and the slicing note written down 15%

Related: Mini projects · Project roadmap · All projects · Module 8 — Abstract Classes