[C++] polymorphism and multiple inheritance

hello,

This is more a C++ and Object Oriented Programming question than an openFrameworks one :

I have an app that needs a printer. This printer can be of multiple types :

  • standard printer via CUPS using a system command line
  • WiFi printer
  • Thermal printer tied to serial port over USB which uses an external addon (ofxThermalPrinter)

I have read a lot about inheritance and polymorphism, but i am afraid i didn’t understand every subtle aspect of it. I am not very familiar with inheritance which i don’t often use, but for this purpose it sounds it would be the wise solution ?

So as i understand, i would need (?) :

  • a BasePrinter class
  • a PrinterCUPS class derived from BasePrinter
  • a PrinterWIFI class derived from BasePrinter
  • a PrinterThermal class derived from BasePrinter and ofxThermalPrinter

now, here are some constraints

  • All printers have a print() method
  • only WiFi and Serial printers have a close() method

From what i see for my purpose, i don’t think i would need the BasePrinter as every printer would print differently.

So i imagine i would need abstract class ?

this is what i have so far (just for ThermalPrinter type)

class Printer
{
public:    
    Printer() {};
    virtual ~Printer() {};
    
    virtual void print() = 0;
}
#include <printer.h>
#include <ofxThermalPrinter.h>

class ThermalPrinter : public Printer, public ofxThermalPrinter {
public:
    ThermalPrinter();
    virtual ~ThermalPrinter();
    
    virtual void print();
    
};

Do i need to use pointers ?
How to manage use of those printers ? do i have to declare BasePrinter objects or specific objects related to their nature (CUPS, WiFi etc…) ?

If anyone could help me sort this out, this would be great !

thanks a lot

This is just a pragmatic advice, not too much of a software engineering one:

For your need, you can see your base class as the class you want to interact with, while not having to worry about the underlying details. So you could define your base class with all the functions you may need (open, print, close), and if a specific printer doesn’t need that function, just make the implementation for that specific printer return immediately.

A decent example of this approach is for ofVideoPlayer that hides the complexity of interfacing with various native code and offer a common interface to all platforms.

Note: the problem with this approach is that it’s really rigid and if you need to make structural changes to your code it can get painful (for example with ofVideoPlayer, the update() function is not virtual ( I believe) and will copy the pixels given by the native video player into a texture. But if you have a special video player that gives you a texture, and you’d want to get the pixels (ie the opposite process) everything falls apart because of the early assumption Pixels go to texture and not the other way)

@Gallo the way you are doing it is correct, just define everything as pure virtual, virtual void func()=0; since your classes don’t share any implementation details. that still allows you to use objects of different types on a vector for example (polymorphishm) but as you say you need to use pointers then.

You can create your objects normally but if you need to use them as the base class then you need a pointer or reference, smart pointers like shared_ptr or unique_ptr also work.

Putting implementation details or variables into base classes (inheritance) is usually a source of problems and makes the code hard to understand if you need some common functionality it’s usually better to have an external class or function that you call from everywhere that implements that funcitonality. For example instead of:

class BasePrinter{
public:
    void format(Format f, Doc doc){
          //implementation
    }
};

class SomePrinter: BasePrinter{
public:
     void print(Doc doc){
         format(doc);
     }
};

where looking at print implementation is easy to not find out where that format method is implemented, you can do:

class Formatter{
public:
    void format(Format f, Doc doc){
          //implementation
    }
};

class SomePrinter: BasePrinter{
    Formatter formatter;
public:
     void print(Doc doc){
         formatter.format(doc);
     }
};

which is as reusable or even more and makes it easier to understand where that code comes from.