C++ Class Design and Implementation

This article explores the design and implementation of several C++ classes, focusing on encapsulation, constructors, destructors, static members, and operator overloading.

Task 1: Class T

The following code defines a simple class T with basic member functions and constructors.

#include "ClassT.h"
#include <iostream>
#include <string>

const std::string ClassT::description{"Example class T"};
const int ClassT::max_count = 999;
int ClassT::instance_count = 0;

int ClassT::getInstanceCount() {
    return instance_count;
}

ClassT::ClassT(int x, int y) : value1{x}, value2{y} {
    ++instance_count;
    std::cout << "ClassT constructor invoked.\n";
}

ClassT::ClassT(const ClassT &obj) : value1{obj.value1}, value2{obj.value2} {
    ++instance_count;
    std::cout << "ClassT copy constructor invoked.\n";
}

ClassT::ClassT(ClassT &&obj) : value1{obj.value1}, value2{obj.value2} {
    ++instance_count;
    std::cout << "ClassT move constructor invoked.\n";
}

ClassT::~ClassT() {
    --instance_count;
    std::cout << "ClassT destructor invoked.\n";
}

void ClassT::modify(int factor) {
    value1 *= factor;
    value2 *= factor;
}

void ClassT::show() const {
    std::cout << "(" << value1 << ", " << value2 << ")";
}

void utilityFunction() {
    ClassT obj5(42);
    obj5.value2 = 2049;
    std::cout << "obj5 = "; obj5.show(); std::cout << '\n';
}

The header file for this class includes:

#pragma once

#include <string>

class ClassT {
public:
    ClassT(int x = 0, int y = 0);
    ClassT(const ClassT &obj);
    ClassT(ClassT &&obj);
    ~ClassT();

    void modify(int factor);
    void show() const;

private:
    int value1, value2;

public:
    static int getInstanceCount();

public:
    static const std::string description;
    static const int max_count;

private:
    static int instance_count;

    friend void utilityFunction();
};

void utilityFunction();

The main functon demonstrates creating and manipulating instances of ClassT:

#include "ClassT.h"
#include <iostream>

void testClassT();

int main() {
    std::cout << "Testing ClassT:\n";
    testClassT();

    std::cout << "\nTesting friend function:\n";
    utilityFunction();
}

void testClassT() {
    using std::cout;
    using std::endl;

    cout << "ClassT info: " << ClassT::description << endl;
    cout << "Max number of ClassT objects: " << ClassT::max_count << endl;
    cout << "Current number of ClassT objects: " << ClassT::getInstanceCount() << endl << endl;

    ClassT obj1;
    cout << "obj1 = "; obj1.show(); cout << endl;

    ClassT obj2(3, 4);
    cout << "obj2 = "; obj2.show(); cout << endl;

    ClassT obj3(obj2);
    obj3.modify(2);
    cout << "obj3 = "; obj3.show(); cout << endl;

    ClassT obj4(std::move(obj2));
    cout << "obj4 = "; obj4.show(); cout << endl;

    cout << "Test: Current number of ClassT objects: " << ClassT::getInstanceCount() << endl;
}

Task 2: Complex Number Class

The following code defines a ComplexNumber class that supports basic operations.

#include "ComplexNumber.h"
#include <iostream>
#include <cmath>

const std::string ComplexNumber::documentation = "Simplified complex number class";

ComplexNumber::ComplexNumber() : realPart(0.0), imaginaryPart(0.0) {}

ComplexNumber::ComplexNumber(double real) : realPart(real), imaginaryPart(0.0) {}

ComplexNumber::ComplexNumber(double real, double imag) : realPart(real), imaginaryPart(imag) {}

ComplexNumber::ComplexNumber(const ComplexNumber& other) : realPart(other.realPart), imaginaryPart(other.imaginaryPart) {}

double ComplexNumber::getReal() const {
    return realPart;
}

double ComplexNumber::getImaginary() const {
    return imaginaryPart;
}

void ComplexNumber::add(const ComplexNumber& other) {
    realPart += other.realPart;
    imaginaryPart += other.imaginaryPart;
}

void print(const ComplexNumber& c) {
    std::cout << c.realPart;
    if (c.imaginaryPart >= 0) {
        std::cout << " + " << c.imaginaryPart << "i";
    } else {
        std::cout << " - " << -c.imaginaryPart << "i";
    }
}

double magnitude(const ComplexNumber& c) {
    return std::sqrt(c.realPart * c.realPart + c.imaginaryPart * c.imaginaryPart);
}

ComplexNumber sum(const ComplexNumber& c1, const ComplexNumber& c2) {
    return ComplexNumber(c1.realPart + c2.realPart, c1.imaginaryPart + c2.imaginaryPart);
}

bool isEqual(const ComplexNumber& c1, const ComplexNumber& c2) {
    return (c1.realPart == c2.realPart) && (c1.imaginaryPart == c2.imaginaryPart);
}

bool isNotEqual(const ComplexNumber& c1, const ComplexNumber& c2) {
    return !isEqual(c1, c2);
}

The header file for the ComplexNumber class is:

#ifndef COMPLEXNUMBER_H
#define COMPLEXNUMBER_H

#include <string>

class ComplexNumber {
public:
    static const std::string documentation;

    ComplexNumber();
    ComplexNumber(double real);
    ComplexNumber(double real, double imag);
    ComplexNumber(const ComplexNumber& other);
    double getReal() const;
    double getImaginary() const;
    void add(const ComplexNumber& other);

    friend void print(const ComplexNumber& c);
    friend double magnitude(const ComplexNumber& c);
    friend ComplexNumber sum(const ComplexNumber& c1, const ComplexNumber& c2);
    friend bool isEqual(const ComplexNumber& c1, const ComplexNumber& c2);
    friend bool isNotEqual(const ComplexNumber& c1, const ComplexNumber& c2);

private:
    double realPart;
    double imaginaryPart;
};

void print(const ComplexNumber& c);
double magnitude(const ComplexNumber& c);
ComplexNumber sum(const ComplexNumber& c1, const ComplexNumber& c2);
bool isEqual(const ComplexNumber& c1, const ComplexNumber& c2);
bool isNotEqual(const ComplexNumber& c1, const ComplexNumber& c2);

#endif

The main function tests both the custom ComplexNumber class and the stendard library std::complex class:

#include <iostream>
#include <iomanip>
#include <complex>
#include "ComplexNumber.h"

void testCustomComplex();
void testStdComplex();

int main() {
    std::cout << "***** Test 1: Custom ComplexNumber class *****\n";
    testCustomComplex();
    std::cout << "\n***** Test 2: Standard library complex class *****\n";
    testStdComplex();
}

void testCustomComplex() {
    using std::cout;
    using std::endl;
    using std::boolalpha;
    cout << "Class member test:\n";
    cout << ComplexNumber::documentation << endl << endl;
    cout << "ComplexNumber object test:\n";
    ComplexNumber cn1;
    ComplexNumber cn2(3, -4);
    ComplexNumber cn3(cn2);
    ComplexNumber cn4 = cn2;
    const ComplexNumber cn5(3.5);

    cout << "cn1 = "; print(cn1); cout << endl;
    cout << "cn2 = "; print(cn2); cout << endl;
    cout << "cn3 = "; print(cn3); cout << endl;
    cout << "cn4 = "; print(cn4); cout << endl;
    cout << "cn5.real = " << cn5.getReal() 
         << ", cn5.imag = " << cn5.getImaginary() << endl << endl;
    cout << "Complex number operation test:\n";
    cout << "magnitude(cn2) = " << magnitude(cn2) << endl;
    cn1.add(cn2);
    cout << "cn1 += cn2, cn1 = "; print(cn1); cout << endl;
    cout << boolalpha;
    cout << "cn1 == cn2 : " << isEqual(cn1, cn2) << endl;
    cout << "cn1 != cn2 : " << isNotEqual(cn1, cn2) << endl;
    cn4 = sum(cn2, cn3);
    cout << "cn4 = cn2 + cn3, cn4 = "; print(cn4); cout << endl;
}

void testStdComplex() {
    using std::cout;
    using std::endl;
    using std::boolalpha;
    cout << "std::complex<double> object test:\n";
    std::complex<double> cn1;
    std::complex<double> cn2(3, -4);
    std::complex<double> cn3(cn2);
    std::complex<double> cn4 = cn2;
    const std::complex<double> cn5(3.5);
    cout << "cn1 = " << cn1 << endl;
    cout << "cn2 = " << cn2 << endl;
    cout << "cn3 = " << cn3 << endl;
    cout << "cn4 = " << cn4 << endl;
    cout << "cn5.real = " << cn5.real() 
         << ", cn5.imag = " << cn5.imag() << endl << endl;
    cout << "Complex number operation test:\n";
    cout << "magnitude(cn2) = " << std::abs(cn2) << endl;
    cn1 += cn2;
    cout << "cn1 += cn2, cn1 = " << cn1 << endl;
    cout << boolalpha;
    cout << "cn1 == cn2 : " << (cn1 == cn2)<< endl;
    cout << "cn1 != cn2 : " << (cn1 != cn2) << endl;
    cn4 = cn2 + cn3;
    cout << "cn4 = cn2 + cn3, cn4 = " << cn4 << endl;
}

Task 3: Player Control Class

This task involves a MusicController class that parses and executes player commands.

#include "MusicController.h"
#include <iostream>
#include <algorithm>

int MusicController::totalCommands = 0;

MusicController::MusicController() {}

std::string toLowerCase(const std::string& input) {
    std::string result;
    for (char c : input) {
        result += std::tolower(static_cast<unsigned char>(c));
    }
    return result;
}

CommandType MusicController::interpret(const std::string& commandStr) {
    std::string lowerCmd = toLowerCase(commandStr);
    CommandType cmd = CommandType::Unknown;
    if (lowerCmd == "play") {
        cmd = CommandType::Play;
    } else if (lowerCmd == "pause") {
        cmd = CommandType::Pause;
    } else if (lowerCmd == "next") {
        cmd = CommandType::Next;
    } else if (lowerCmd == "prev") {
        cmd = CommandType::Previous;
    } else if (lowerCmd == "stop") {
        cmd = CommandType::Stop;
    }
    if (cmd != CommandType::Unknown) {
        totalCommands++;
    }

    return cmd;
}

void MusicController::perform(CommandType action) const {
    switch (action) {
        case CommandType::Play: std::cout << "[Play] Playing music...\n"; break;
        case CommandType::Pause: std::cout << "[Pause] Music paused\n"; break;
        case CommandType::Next: std::cout << "[Next] Skipping to next track\n"; break;
        case CommandType::Previous: std::cout << "[Previous] Back to previous track\n"; break;
        case CommandType::Stop: std::cout << "[Stop] Music stopped\n"; break;
        default: std::cout << "[Error] Unknown command\n"; break;
    }
}

int MusicController::getCommandCount() {
    return totalCommands;
}

The header file for the MusicController class:

#pragma once

#include <string>

enum class CommandType { Play, Pause, Next, Previous, Stop, Unknown };

class MusicController {
public:
    MusicController();

    static int getCommandCount();
    CommandType interpret(const std::string& commandStr);
    void perform(CommandType action) const;

private:
    static int totalCommands;
};

The main function allows user interaction with the music controller:

#include "MusicController.h"
#include <iostream>

void runTests() {
    MusicController controller;
    std::string commandStr;
    std::cout << "Enter command: (play/pause/next/prev/stop/quit):\n";
    while (std::cin >> commandStr) {
        if (commandStr == "quit")
            break;

        CommandType cmd = controller.interpret(commandStr);
        controller.perform(cmd);
        std::cout << "Total commands processed: " << MusicController::getCommandCount() << "\n\n";
    }
}

int main() {
    runTests();
}

Task 4: Fraction Class

This task focuses on a RationalNumber class that handles arithmetic operations with fractions.

#include "RationalNumber.h"
#include <iostream>
#include <stdexcept>
#include <cmath>

const std::string RationalNumber::description = "RationalNumber class v 0.01\nSupports construction, output, addition, subtraction, multiplication, and division.";

int RationalNumber::greatestCommonDivisor(int a, int b) {
    a = std::abs(a);
    b = std::abs(b);
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

void RationalNumber::reduce() {
    if (denominator == 0)
        return;

    if (denominator < 0) {
        numerator = -numerator;
        denominator = -denominator;
    }

    int divisor = greatestCommonDivisor(numerator, denominator);
    if (divisor != 0) {
        numerator /= divisor;
        denominator /= divisor;
    }
}

RationalNumber::RationalNumber(int num, int denom) : numerator(num), denominator(denom) {
    if (denominator == 0)
        return;
    reduce();
}

RationalNumber::RationalNumber(const RationalNumber& other) : numerator(other.numerator), denominator(other.denominator) {}

int RationalNumber::getNumerator() const {
    return numerator;
}

int RationalNumber::getDenominator() const {
    return denominator;
}

RationalNumber RationalNumber::negate() const {
    return RationalNumber(-numerator, denominator);
}

void display(const RationalNumber& frac) {
    if (frac.getDenominator() == 0) {
        std::cout << "Denominator cannot be zero";
        return;
    }

    int num = frac.getNumerator();
    int denom = frac.getDenominator();

    if (denom == 1) {
        std::cout << num;
    } else if (num == 0) {
        std::cout << "0";
    } else {
        std::cout << num << "/" << denom;
    }
}

RationalNumber addFractions(const RationalNumber& frac1, const RationalNumber& frac2) {
    if (frac1.getDenominator() == 0 || frac2.getDenominator() == 0)
        return RationalNumber(0, 0);

    int newNum = frac1.getNumerator() * frac2.getDenominator() + frac2.getNumerator() * frac1.getDenominator();
    int newDenom = frac1.getDenominator() * frac2.getDenominator();
    return RationalNumber(newNum, newDenom);
}

RationalNumber subtractFractions(const RationalNumber& frac1, const RationalNumber& frac2) {
    if (frac1.getDenominator() == 0 || frac2.getDenominator() == 0)
        return RationalNumber(0, 0);

    int newNum = frac1.getNumerator() * frac2.getDenominator() - frac2.getNumerator() * frac1.getDenominator();
    int newDenom = frac1.getDenominator() * frac2.getDenominator();
    return RationalNumber(newNum, newDenom);
}

RationalNumber multiplyFractions(const RationalNumber& frac1, const RationalNumber& frac2) {
    if (frac1.getDenominator() == 0 || frac2.getDenominator() == 0)
        return RationalNumber(0, 0);

    int newNum = frac1.getNumerator() * frac2.getNumerator();
    int newDenom = frac1.getDenominator() * frac2.getDenominator();
    return RationalNumber(newNum, newDenom);
}

RationalNumber divideFractions(const RationalNumber& frac1, const RationalNumber& frac2) {
    if (frac1.getDenominator() == 0 || frac2.getDenominator() == 0 || frac2.getNumerator() == 0)
        return RationalNumber(0, 0);

    int newNum = frac1.getNumerator() * frac2.getDenominator();
    int newDenom = frac1.getDenominator() * frac2.getNumerator();
    return RationalNumber(newNum, newDenom);
}

The header file for the RationalNumber class:

#ifndef RATIONALNUMBER_H
#define RATIONALNUMBER_H

#include <string>

class RationalNumber {
public:
    static const std::string description;

    RationalNumber(int num = 0, int denom = 1);
    RationalNumber(const RationalNumber& other);

    int getNumerator() const;
    int getDenominator() const;
    RationalNumber negate() const;

private:
    int numerator;
    int denominator;

    void reduce();
    static int greatestCommonDivisor(int a, int b);
};

void display(const RationalNumber& frac);
RationalNumber addFractions(const RationalNumber& frac1, const RationalNumber& frac2);
RationalNumber subtractFractions(const RationalNumber& frac1, const RationalNumber& frac2);
RationalNumber multiplyFractions(const RationalNumber& frac1, const RationalNumber& frac2);
RationalNumber divideFractions(const RationalNumber& frac1, const RationalNumber& frac2);

#endif

The main function tests the RationalNumber class functionality:

#include "RationalNumber.h"
#include <iostream>

void testBasicFunctionality();
void testZeroDenominator();

int main() {
    std::cout << "Test 1: RationalNumber class basic functionality\n";
    testBasicFunctionality();
    std::cout << "\nTest 2: Zero denominator test:\n";
    testZeroDenominator();
}

void testBasicFunctionality() {
    using std::cout;
    using std::endl;
    cout << "RationalNumber class test:\n";
    cout << RationalNumber::description << endl << endl;
    RationalNumber r1(5);
    RationalNumber r2(3, -4), r3(-18, 12);
    RationalNumber r4(r3);
    cout << "r1 = "; display(r1); cout << endl;
    cout << "r2 = "; display(r2); cout << endl;
    cout << "r3 = "; display(r3); cout << endl;
    cout << "r4 = "; display(r4); cout << endl;
    const RationalNumber r5(r4.negate());
    cout << "r5 = "; display(r5); cout << endl;
    cout << "r5.getNumerator() = " << r5.getNumerator()
         << ", r5.getDenominator() = " << r5.getDenominator() << endl;
    cout << "r1 + r2 = "; display(addFractions(r1, r2)); cout << endl;
    cout << "r1 - r2 = "; display(subtractFractions(r1, r2)); cout << endl;
    cout << "r1 * r2 = "; display(multiplyFractions(r1, r2)); cout << endl;
    cout << "r1 / r2 = "; display(divideFractions(r1, r2)); cout << endl;
    cout << "r4 + r5 = "; display(addFractions(r4, r5)); cout << endl;
}

void testZeroDenominator() {
    using std::cout;
    using std::endl;
    RationalNumber r6(42, 55), r7(0, 3);
    cout << "r6 = "; display(r6); cout << endl;
    cout << "r7 = "; display(r7); cout << endl;
    cout << "r6 / r7 = "; display(divideFractions(r6, r7)); cout << endl;
}

Tags: C++ OOP Constructors destructors static-members

Posted on Wed, 14 Oct 2026 16:10:30 +0000 by ziesje