Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Advanced C++ Features: Polymorphism, Overloading, and Generics

Tech 1

Runtime Polymorphism with Abstract Base Classes

Defining an abstract base class allows for a unified interface across diverse derived types. The following hierarchy demonstrates a content management system where different media types share common behaviors but implement them uniquely.

Media Hierarchy Implementation

The header file defines the interface. Note the use of pure virtual functions to enforce implementation in derived classes.

#pragma once
#include <string>

class ContentNode {
public:
    explicit ContentNode(const std::string &title_); 
    virtual ~ContentNode() = default;
    
    virtual void display_info() const = 0;
    virtual void consume() const = 0;
    
protected:
    std::string title;
};

class Novel : public ContentNode {
public:
    Novel(const std::string &title_, const std::string &writer_);
    void display_info() const override;
    void consume() const override;
private:
    std::string writer;
};

class Movie : public ContentNode {
public:
    Movie(const std::string &title_, const std::string &director_);
    void display_info() const override;
    void consume() const override;
private:
    std::string director;
};

class Song : public ContentNode {
public:
    Song(const std::string &title_, const std::string &performer_);
    void display_info() const override;
    void consume() const override;
private:
    std::string performer;
};

The implementation file provides the logic for each media type. Each class initializes its base class and specific members via the constructor initialization list.

#include <iostream>
#include "content_node.hpp"

ContentNode::ContentNode(const std::string &title_) : title{title_} {}

Novel::Novel(const std::string &title_, const std::string &writer_) 
    : ContentNode{title_}, writer{writer_} {}

void Novel::display_info() const {
    std::cout << "Release: Novel [" << title << "] by " << writer << '\n';
}

void Novel::consume() const {
    std::cout << "Action: Reading [" << title << "]\n";
}

Movie::Movie(const std::string &title_, const std::string &director_) 
    : ContentNode{title_}, director{director_} {}

void Movie::display_info() const {
    std::cout << "Release: Movie <" << title << "> directed by " << director << '\n';
}

void Movie::consume() const {
    std::cout << "Action: Watching <" << title << ">\n";
}

Song::Song(const std::string &title_, const std::string &performer_) 
    : ContentNode{title_}, performer{performer_} {}

void Song::display_info() const {
    std::cout << "Release: Track " << title << " by " << performer << '\n';
}

void Song::consume() const {
    std::cout << "Action: Listening to " << title << '\n';
}

Memory Management Strategies

When storing polymorphic objects, raw pointers require manual deletion, whereas smart pointers automate resource management. The virtual destructor in the base class ensures derived destructors are called correctly.

#include <memory>
#include <vector>
#include <iostream>
#include "content_node.hpp"

void demonstrate_raw_pointers() {
    std::vector<ContentNode*> collection;
    collection.push_back(new Novel("Dune", "Frank Herbert"));
    collection.push_back(new Movie("Inception", "Christopher Nolan"));
    
    for(auto* item : collection) {
        item->display_info();
        item->consume();
        delete item;
    }
}

void demonstrate_smart_pointers() {
    std::vector<std::unique_ptr<ContentNode>> collection;
    collection.push_back(std::make_unique<Novel("1984", "George Orwell"));
    collection.push_back(std::make_unique<Song("Bohemian Rhapsody", "Queen"));
    
    for(const auto& item : collection) {
        item->display_info();
        item->consume();
    }
}

int main() {
    demonstrate_smart_pointers();
    return 0;
}

Operator Overloading and Object Composition

Complex data structures often require custom output formatting and composition relationships. This section illustrates a sales tracking system where a transaction record contains a product description.

Product and Sales Record Classes

The Product class holds static information, while SalesRecord combines product data with dynamic sales metrics. The insertion operator is overloaded to facilitate clean printing.

#pragma once
#include <string>
#include <iostream>

class Product {
public:
    Product(const std::string &n, const std::string &a, 
            const std::string &i, double p);
    friend std::ostream& operator<<(std::ostream &out, const Product &p);
private:
    std::string name;
    std::string author;
    std::string isbn;
    double price;
};

class SalesRecord {
public:
    SalesRecord(const Product &p, double sell_price, int qty);
    int get_quantity() const;
    double calculate_revenue() const;
    friend std::ostream& operator<<(std::ostream &out, const SalesRecord &rec);
private:
    Product item;
    double sell_price;
    int quantity;
};
#include <iomanip>
#include "sales.hpp"

Product::Product(const std::string &n, const std::string &a, 
                 const std::string &i, double p)
    : name{n}, author{a}, isbn{i}, price{p} {}

std::ostream& operator<<(std::ostream &out, const Product &p) {
    out << std::left << std::setw(10) << "Title:" << p.name << '\n'
        << std::setw(10) << "Author:" << p.author << '\n'
        << std::setw(10) << "ISBN:" << p.isbn << '\n'
        << std::setw(10) << "List Price:" << p.price;
    return out;
}

SalesRecord::SalesRecord(const Product &p, double sell_price, int qty)
    : item{p}, sell_price{sell_price}, quantity{qty} {}

double SalesRecord::calculate_revenue() const {
    return quantity * sell_price;
}

std::ostream& operator<<(std::ostream &out, const SalesRecord &rec) {
    out << rec.item << '\n'
        << std::setw(10) << "Sold For:" << rec.sell_price << '\n'
        << std::setw(10) << "Volume:" << rec.quantity << '\n'
        << std::setw(10) << "Total:" << rec.calculate_revenue();
    return out;
}

Sorting Sales Data

Records can be organized using standard algorithms. A custom comparator allows sorting based on specific metrics like sales volume.

#include <vector>
#include <algorithm>
#include "sales.hpp"

bool compare_by_volume(const SalesRecord &a, const SalesRecord &b) {
    return a.get_quantity() > b.get_quantity();
}

void process_sales_data() {
    std::vector<SalesRecord> ledger;
    // Assume data population here
    // ledger.push_back(...);
    
    std::sort(ledger.begin(), ledger.end(), compare_by_volume);
    
    for(const auto &entry : ledger) {
        std::cout << entry << "\n----------------\n";
    }
}

Generic Programming with Class Templates

Templates eliminate code duplication when logic is identical across different data types. Comparing specific classes against a generic template highlights the reduction in boilerplate code.

Specific vs. Generic Coordinates

Without templates, separate classes are needed for integers and doubles. A template class generalizes this behavior.

#include <iostream>
#include <string>

// Specific implementation for integers
class CoordInt {
public:
    CoordInt(int x, int y) : x_val{x}, y_val{y} {}
    void show() const { std::cout << x_val << ", " << y_val << '\n'; }
private:
    int x_val, y_val;
};

// Specific implementation for doubles
class CoordDouble {
public:
    CoordDouble(double x, double y) : x_val{x}, y_val{y} {}
    void show() const { std::cout << x_val << ", " << y_val << '\n'; }
private:
    double x_val, y_val;
};

// Generic template implementation
template<typename T>
class CoordGeneric {
public:
    CoordGeneric(T x, T y) : x_val{x}, y_val{y} {}
    void show() const { std::cout << x_val << ", " << y_val << '\n'; }
private:
    T x_val, y_val;
};

int main() {
    CoordInt ci(10, 20);
    ci.show();
    
    CoordGeneric<double> cg(10.5, 20.5);
    cg.show();
    
    CoordGeneric<std::string> cs("A", "B");
    cs.show();
    
    return 0;
}

Arithmetic Templates: Complex Number Example

Templates are particularly useful for mathematical structures where operations remain consistent regardless of the underlying scalar type. The following class supports arithmetic operations and stream I/O.

Template Class Definition

This implementation handles real and imaginary parts generically. Friend functions are used to allow access to private members during operator overloading.

#ifndef NUMERIC_COMPLEX_HPP
#define NUMERIC_COMPLEX_HPP

#include <iostream>

template<typename T>
class NumericComplex {
private:
    T re;
    T im;

public:
    NumericComplex() : re(0), im(0) {}
    NumericComplex(T r, T i = 0) : re(r), im(i) {}
    NumericComplex(const NumericComplex& other) : re(other.re), im(other.im) {}

    T real_part() const { return re; }
    T imag_part() const { return im; }

    NumericComplex& operator+=(const NumericComplex& other) {
        re += other.re;
        im += other.im;
        return *this;
    }

    friend NumericComplex operator+(const NumericComplex& l, const NumericComplex& r) {
        return NumericComplex(l.re + r.re, l.im + r.im);
    }

    bool operator==(const NumericComplex& other) const {
        return (re == other.re) && (im == other.im);
    }

    friend std::ostream& operator<<(std::ostream& os, const NumericComplex<T>& val) {
        os << val.re;
        if (val.im >= T(0)) {
            os << " + " << val.im << "i";
        } else {
            os << " - " << (-val.im) << "i";
        }
        return os;
    }

    friend std::istream& operator>>(std::istream& is, NumericComplex<T>& val) {
        T r, i;
        if (is >> r >> i) {
            val.re = r;
            val.im = i;
        }
        return is;
    }
};

#endif

Usage Demonstration

The template works seamlessly with both integer and floating-point types, demonstrating type safety and code reuse.

#include "NumericComplex.hpp"

void run_tests() {
    NumericComplex<int> c1(2, -5);
    NumericComplex<int> c2 = c1;

    std::cout << "Value 1: " << c1 << '\n';
    std::cout << "Value 2: " << c2 << '\n';
    std::cout << "Sum: " << (c1 + c2) << '\n';
    
    c1 += c2;
    std::cout << "Updated Value 1: " << c1 << '\n';
    std::cout << "Equality Check: " << (c1 == c2) << '\n';
}

int main() {
    run_tests();
    return 0;
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.