Abstract base class

G'day

i have written a library that currently has different functions for different versions of a family of parts but which essentially do the same thing.
I was recommended to use an abstract base class and then different subclasses but i do not know how to implement this.

The point would be that the functions would be the of the same name, but pass and return different data types depending on the version of the part passed by the constructor.

Does anyone have any examples for this kind of coding

thanks

Here is some reading to keep you busy.

http://www.cplusplus.com/doc/tutorial/inheritance/
http://www.cplusplus.com/doc/tutorial/polymorphism/
http://www.angelfire.com/tx4/cus/shapes/cpp.html

Hi,
You can return different 'data' this way, but not different 'data types'. I assume you meant different data.

Duane B

rcarduino.blogspot.com

You can return different 'data' this way, but not different 'data types'. I assume you meant different data

You can, by overloading the inherited members. To overload a member, the parameter list must be different in some way that can disambiguate it from the other overloads, there are no restrictions on the return types. Pure virtual functions are a different kettle of fish however.

Hi,
If you overload the function prototype, the assumption is that the caller knows this, what the OP is looking for is to abstract the different hardware so that the caller does not need to know these details.

OP, correct me if I am wrong and you want you callers to know that they have to pass or receive different data types for different devices ?

Duane B

rcarduino.blogspot.com

Overloading can only be done by varying the argument list. The return type must be the same.

Now what about some code :slight_smile:

Overloading can only be done by varying the argument list. The return type must be the same.

That is not quite correct.
The argument list must be different, but return types can be anything, the limitation is when using pure virtual functions, not virtual functions.

If a base member has a certain prototype, a user can expect a certain functionality in derived types, so it is perfectly valid for a common base set of overloads to return different types.

It is not 'valid' when the overload introduces uniqueness into the derived types, and a unique member should be used rather than an overloaded member.

Hi,

Unfortunately I suspect that none of this is very encouraging to the OP.

I would suggest that if the behavior and the code to implement this behavior is largely the same across devices, you can create a base class that provides a full solution for the most common device but which includes virtual functions for the behavior which is different in other devices.

If the code to implement the largely similar behavior is actually quite different between devices then you would be better creating an abstract base class rather than the suggestion above - of a base class, because the base class code would be irrelevant in many cases.

The decision is really down to the extent of the device specifics -

  1. Everything about the devices is different, I just want to provide users with a class that hides this

Go with an abstract base class and create full implementations of classes for each device

  1. Mostly the devices are very similar, but there are some minor differences in how they are accessed and what they return

In this case there is an opportunity to provide a base class which provides a full implementation of the most common device and derived classes which implement/hide the differences through virtual functions.

These are often qualitative decisions, If I were you I would start with a base class that implements the most common device, then try and add additional devices through derived classes, if this starts to get awkward then its telling you that you would be better of with an abstract base class and individual implementations in the derived classes.

Duane B

rcarduino.blogspot.com

pYro_65:

Overloading can only be done by varying the argument list. The return type must be the same.

That is not quite correct.
The argument list must be different, but return types can be anything, the limitation is when using pure virtual functions, not virtual functions.

If a base member has a certain prototype, a user can expect a certain functionality in derived types, so it is perfectly valid for a common base set of overloads to return different types.

It is not 'valid' when the overload introduces uniqueness into the derived types, and a unique member should be used rather than an overloaded member.

When I said the return type has to be the same I was thinking about code where the only thing that changes is the return type of the function.

Example A:

class OverloadTest {
    int sum(int a, int b) { return a+b; };
    float sum(int a, int b) { return (float)a + (float)b; };
};

Example B:

class BaseClass {
    virtual int sum(int a, int b) { return a+b; };
};

class DerivedClass : public OverloadTest {
    virtual float sum(int a, int b) { return (float)a + (float)b; };
};

Trying to compile Example A gives:

error: ‘float OverloadTest::sum(int, int)’ cannot be overloaded with ‘int OverloadTest::sum(int, int)’

Trying to compile Example B gives:

error: conflicting return type specified for ‘virtual float DerivedClass::sum(int, int)’

Of course I was wrong when I stated the return type must be the same, because if one changes the method argument list, then the return type can also be different:

class OverloadTest {
    int sum(int a, int b) { return a+b; };
    float sum(float a, float b) { return (float)a + (float)b; };
};
class BaseClass {
    virtual int sum(int a, int b) { return a+b; };
};

class DerivedClass : public BaseClass {
    virtual float sum(float a, float b) { return (float)a + (float)b; };
};

DuaneB's response is sound. It should be printed in A3 and kept beside the monitor :slight_smile:

bungalally:
Does anyone have any examples for this kind of coding

This is an example of base and derived classes:

// abstract base class
class baseClass 
  {
  public:
  virtual int foo (const int bar) = 0;
  }; // end of baseClass

// derived class 1
class buttonClass : baseClass
  {
  public:
  virtual int foo (const int bar)
     {
     return bar * 2;  
     }
  }; // end of buttonClass

// derived class 2
class switchClass : baseClass
  {
  public:
  virtual int foo (const int bar)
     {
     return bar * 3;  
     }
  }; // end of switchClass
  
void setup ()
{
  Serial.begin (115200);
//  baseClass a;  // <-- error, you can't make an instance of an abstract class
  buttonClass b;
  switchClass c;
  Serial.println (b.foo (5));
  Serial.println (c.foo (8));
}  // end of setup

void loop () {}

Output:

10
24

bungalally:
The point would be that the functions would be the of the same name, but pass and return different data types depending on the version of the part passed by the constructor.

You may not need a class at all. You can make multiple functions with different data types, of the same name, without using classes or base classes.

And if they do something virtually identical (eg. add things together) then you can use templates.

Example:

File: template_test.h

template<typename T> T add (const T x, const T y)
  {
  return x + y;
  }  // end of add

Sketch:

#include "template_test.h"
  
void setup ()
{
  Serial.begin (115200);
  int a = 5, b = 7;
  Serial.println (add (a, b));
  float c = 6.2, d = 7.3;
  Serial.println (add (c, d));
  long e = 100000, f = 500000;
  Serial.println (add (e, f));
}  // end of setup
void loop () {}

The templated class "add" adds together any two things of the same type (eg. int, byte, char, float, unsigned long) and returns the same type. The type in the template is called T.

In the main sketch the compiler chooses the correct version of the function based on the arguments to it (eg. if you pass ints to add, you get an int back).

Output:

12
13.50
600000

Virtual functions implement runtime polymorphism. This is great when you have parts of the code that don't know exactly what the end requirement is. For example, a user in a drawing program can choose to draw a triangle, or draw a circle, in a drawing program. Or, in a PC, a user may plug in some Bose USB speakers, or a Logitech headset, each of which has different volume controls.

For AVR code, however, pretty much everything is statically knowable. You don't generally hot-plug hardware devices, nor implement large and varying desktop applications in a microcontroller based system, so you generally don't need runtime polymorphism -- if you have an elaborate menu system, you might make use of it, but for hardware, I don't think it's important.

If your use case is "I want to support building code for a wide variety of circuits," then it's more important to support the same static API. This means that you can "swap out" the constructed type at compile time, and still have code work the same. If you need easy variability for the runtime type (for advanced memory management or whatever -- unlikely on an AVR) then you can look into using templates.

My suggestion: Make sure the classes all have the same API, but it doesn't sound as if you actually NEED the base class/virtuals. And there is an actual cost to calling a virtual function.

Good class design wont suffer from these overheads. If your classes are transparent enough the compiler can make its way to the base classes and optimise away the virtual functions. I'm working on a HAL for arduino, it uses nothing but classes. In some cases the classes are entirely optimised out and direct port mapping is generated in its place, this is using 4 tiers of inheritance.

Check out 'Curiously recurring template pattern' ( Static polymorphism ) and Template Meta-programs.

Ok so i think the way i should implement this is with a templated function.
i probably should of explained a bit more of what i was doing but anyway.

this is part of my original code, for reading from an eeprom chip.

byte at93c::readbyte(int address){
  byte datain;
  int _address = (address << (16 - _addresslength));
  
  digitalWrite(_cspin, HIGH);
  _shiftOut(_dopin, _skpin, B110, 3);
  _shiftOut(_dopin, _skpin, _address, _addresslength);
  datain = _shiftIn(_dipin, _skpin, MSBFIRST, _datalength);
  digitalWrite(_cspin, LOW);
  return datain;
}
word at93c::readword(int address){
  word datain;
  int _address = (address << (16 - _addresslength));
  digitalWrite(_cspin, HIGH);
  _shiftOut(_dopin, _skpin, B110, 3);
  _shiftOut(_dopin, _skpin, _address, _addresslength);
  datain = _shiftIn(_dipin, _skpin, _datalength);
  digitalWrite(_cspin, LOW);
  return datain;
}

Based either on the setting of an organisation pin or the design of the part each address stores/reads either 8 bits or 16bits.
What I want to get rid of is duplicating function read for bytes or words.
for writing to the chip, the data type (byte/word) can be inferred from the data passed to the writing function.
as in the example given by Nick

template<typename T> T add (const T x, const T y)
  {
  return x + y;
  }  // end of add

the problem i face is for reading the data the returned type cannot be inferred from the arguments but must come from a the organisation variable (org) given in the contructor.

i.e
if org = 8 read(int address) returns byte
if org = 16 read(int address) returns word

Well all you need is the size, and you can work that out:

#if defined(ARDUINO) && ARDUINO >= 100
  #include "Arduino.h"
#else
  #include "WProgram.h"
#endif

template<typename T> T add (const T x, const T y)
  {
  Serial.print ("Size of data is ");
  Serial.println (sizeof (T));
  
  return x + y;
  }

And in any case it looks like someone has already done the hard work:

http://arduino.cc/playground/Code/EEPROMWriteAnything

i probably should of explained a bit more of what i was doing but anyway.

That can usually save quite a bit of time ... yours and ours.