Search code examples
c++polymorphismmodular

How to avoid duplicating functions with a modular architecture


I am trying to implement the modular architecture with C++ where I have a .h file for every .cpp file. The files currently only have empty functions:

cycle.h

#include <iostream>
#ifndef CYCLIST_H
#define CYCLIST_H
#pragma once

class cyclist{
private:
    double width;
    double length;
    int helment;
public:
    void Predict();
    double get_width();
    double get_length();
    int get_helmet();
};

#endif

cycle.cpp

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

void cyclist::Predict(){std::cout << "Predict something!" << std::endl;}
double cyclist::get_width(){}
double cyclist::get_length(){}
int cyclist::get_helmet(){}

vehicle.h

#include <iostream>
#ifndef VEHICLE_H
#define VEHICLE_H
#pragma once

class vehicle{
private:
    double width;
    double length;
    int number_of_doors;
public:
    void Predict();
    double get_width();
    double get_length();
    int get_number();
};

#endif

vehicle.cpp

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

void vehicle::Predict(){std::cout << "Predict something!" << std::endl;}
double vehicle::get_width(){}
double vehicle::get_length(){}
int vehicle::get_number(){}

As you can see, I am repeating the get_wdith(), get_height(), and Predict() functions. The reason being that each of them will have a different implementation of the same function.

How can I derive all these functions from a base class? If I am using a base class, would/can I still use .h files?

Edit

So what I created was a single header file Base.h like this:

#include <iostream>
#pragma once

class Base{
    public:
        virtual ~Base() = default;
        virtual void Predict() = 0;
        virtual double get_width() = 0;
        virtual double get_length() = 0;
        virtual int get_number_of_doors() = 0;
        virtual bool get_helmet_state();
};

and then I decided to inherit these from cyclist and vehicle classes like this:

cyclist.cpp

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

void Base::Predict(){std::cout << "Predict something!" << std::endl;}
double Base::get_width(){return 0;}
double Base::get_length(){return 0;}
bool Base::get_helmet_state(){return true;}

vehicle.cpp

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

void Base::Predict(){std::cout << "Predict something!" << std::endl;};
double Base::get_width(){return 0;}
double Base::get_length(){return 0;}
int Base::get_number_of_doors(){return 0;}

Now when I run it, I get an error saying that there are multiple definitions of Base::Predict(). This function will be implemented differently in each class. I tried to add the override keyword but it didn't let me.


Solution

  • Step One: Apply Liskov Substitution Principle to make sure inheritance makes sense.

    Step One-A: Make sure Composition isn't a better fit.

    Step Two: Make base class

    class Base // For demonstration purposes only. Give this a more descriptive name in your code
    { 
    public: 
        virtual ~Base() = default;
        virtual void Predict() = 0;
        virtual double get_width() = 0;
        virtual double get_length() = 0;
    };
    

    Feel tree to pop this in its own header if you want. Nothing prevents an inheritance hierarchy from using and being divided up into headers.

    Depending on your use case, you may or may not need the destructor, but if you're learning C++, make your life easier and put it in. Having it may waste some time at runtime, but you'll have fewer weird problems to have to debug if your inexperience leads you down the wrong path.

    All functions you want to override must be declared virtual. virtual is sticky, so you don't have to keep repeating it in the derived classes, but keep an eye out for final.

    The = 0 on the end of the virtual functions makes them pure and makes the class abstract. This may or may not be the behaviour you want, but each of them will have a different implementation strongly hints that it is. Read the linked documentation to be sure.

    Step Three: Inherit

    class cyclist: public Base{
    private:
        double width;
        double length;
        int helment;
    public:
        void Predict() override;
        double get_width() override;
        double get_length() override;
        int get_helmet();
    };
    

    Note that the override keyword is not a hard requirement, but it can save you a lot of trouble by picking off mistakes, costs only a few seconds to type, an insignificant amount of time to compile, and absolutely nothing at runtime. There are a few win-win situations in the world, so take advantage of this one.

    Addendum:

    If you have behaviour shared between classes in the overridden functions, consider moving the code for the shared behaviour into a protected support function and calling the support function from the overridden functions to eliminate the duplication.

    class Base // For demonstration purposes only. Give this a more descriptive name in your code
    { 
    protected: 
        double predict_support();
    public: 
        virtual ~Base() = default;
        virtual void Predict() = 0;
        virtual double get_width() = 0;
        virtual double get_length() = 0;
    };
    

    and later in Base.cpp

    double Base::predict_support()
    {
        return do_complicated_math();
    }
    

    and then use it something like

    void cyclist::Predict()
    {
        std::cout << "Predict something: " << predict_support() << std::endl;
    }