Skip to Content
Course content

192: Practice Exercise: Modeling an Employee Management System

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think about how a company’s HR department actually keeps track of people. They don't just have one giant list where everyone is treated exactly the same. Instead, they have a general "Employee" file—containing the basics like a name and an ID—but then they have different rules depending on the type of employee. A salaried executive gets a fixed monthly check; a freelance consultant gets paid by the hour; a sales rep gets a base salary plus a commission. If the HR manager wants to calculate the total payroll for the month, they don't manually check every person's contract type; they just tell every "Employee" folder to "calculate pay," and the specific rules inside that folder take over.

In C++, we model this using a class hierarchy. The general "Employee file" is your base class, and the specific contract types are your derived classes. Here is how that mapping works in the code we're about to build:

  • The General Folder $\rightarrow$ Base Class: This holds the data every single employee has, regardless of their role (e.g., std::string name).
  • Specific Rules $\rightarrow$ Virtual Functions: The "calculate pay" action is a virtual function. This tells C++, "I don't know the exact math yet, but the specific type of employee will figure it out at runtime."
  • The Filing Cabinet $\rightarrow$ std::vector<Employee*>: To manage everyone together, we store pointers to the base class. This allows us to mix and match salaried and hourly employees in one list while still triggering their specific pay logic.

Defining the Base Employee Template

When I'm designing a system like this, I always start by identifying the "invariant" data. What is true for every single person on the payroll? In our case, it's their ID and their name. We'll put those in a BaseEmployee class. I'm making the destructor virtual here—and I cannot stress this enough—because if you delete a Contractor object through a BaseEmployee pointer, you'll end up with a memory leak if that destructor isn't virtual.

class BaseEmployee {
protected:
    std::string name;
    int employeeId;

public:
    BaseEmployee(std::string n, int id) : name(n), employeeId(id) {}
    virtual ~BaseEmployee() {} 

    virtual double calculatePay() const = 0; // Pure virtual: every employee MUST have a pay method
    std::string getName() const { return name; }
};

Polymorphism in Action: Salaries

Now we get to the part where the system actually does something useful. We create derived classes that implement the calculatePay method. I like to keep these lean. The SalariedEmployee just returns a flat rate, while the HourlyEmployee does a bit of multiplication. By using override, we're telling the compiler to scream at us if we accidentally mess up the function signature.

class SalariedEmployee : public BaseEmployee {
    double annualSalary;
public:
    SalariedEmployee(std::string n, int id, double salary) 
        : BaseEmployee(n, id), annualSalary(salary) {}

    double calculatePay() const override {
        return annualSalary / 12; // Monthly payout
    }
};

class HourlyEmployee : public BaseEmployee {
    double hourlyRate;
    double hoursWorked;
public:
    HourlyEmployee(std::string n, int id, double rate, double hours) 
        : BaseEmployee(n, id), hourlyRate(rate), hoursWorked(hours) {}

    double calculatePay() const override {
        return hourlyRate * hoursWorked;
    }
};

Managing the Organizational Chart

The real magic happens when you treat these different types as a single group. In a real-world app, you wouldn't have separate lists for hourly and salaried staff; that would be a maintenance nightmare. Instead, you use a collection of pointers. I recommend std::unique_ptr to avoid the headache of manual delete calls.

When you loop through this list and call calculatePay(), C++ performs a "dynamic dispatch." It looks at the object, sees "Oh, this one is actually a HourlyEmployee," and jumps to the correct math logic. It's clean, it's scalable, and it means you can add a CommissionEmployee class later without changing a single line of your payroll processing loop.




📋 Practical Task

Implement a Tiered Payroll Processor for a Tech Startup

Your goal is to build a functional prototype of an Employee Management System. You need to implement the following requirements:

  • Create a base class Employee with a virtual destructor and a pure virtual function double calculateMonthlyPay() const.
  • Implement three derived classes:
    • FullTimeEmployee: Takes a monthly salary.
    • PartTimeEmployee: Takes an hourly rate and hours worked.
    • Contractor: Takes a flat project fee and a percentage of completion (e.g., 50% complete of a $10,000 fee).
  • In your main function, create a std::vector of std::unique_ptr<Employee>.
  • Populate the vector with at least one instance of each employee type.
  • Iterate through the vector, printing each employee's name and their calculated monthly pay.
  • Calculate and print the total company payroll expenditure for the month.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.