Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Single Inheritance Classes in C


An Inheritable Class Structure

Inheritance in a C++ context means the ability to both derive a new class from a base class that adds new capability, and perhaps to change some functional aspect of the base class. While C++ has a syntax for this, you must do it yourself in C. The intent of my implementation is to minimize RAM usage and execution time, the former is usually a scarce resource in small microprocessors.

The header file for a derived class includes the same typedef for the class methods as described earlier, but in addition, it now also includes the methods from the base class that are to be available to users of the derived class. I stay with the motor example and derive a class that provides some additional capability, like setting an acceleration rate.

You first need to define the data structure for the derived class. This must be usable by both the derived and base classes. The mechanism I use is based on the fact that, if the base class data structure is the first entry in the derived class data structure, pointers to both a derived and a base class will point at the same data. While this limits the technique to just deriving from one base class (single inheritance), it is rare to find a requirement for multiple inheritance in small embedded systems.

Our derived class data needs to store the acceleration rate as well, so the class data structure becomes Example 4(a). The typedef for the derived class vtable, along with the constructor/destructor and class vtable pointer definitions, looks like Example 4(b).

Example 4: (a) Class data structure; (b) typedef for the derived class vtable, and constructor/destructor and class vtable pointer definitions.
(a)

typedef struct
{
  MotorData  mMotor;
  int        mAccelRate;
} MotorControlData;

(b)

typedef struct
{
    //----------------------------------------------
    // Set the new speed and an accel/decel rate to reach that speed.
    void (*SetNewSpeed)(MotorControlData*, int speed, int rate);

    //----------------------------------------------
    // Base class Motor methods extracted from its typedef.
    void (*InitPort)( MotorData*, int*);
    void (*SetSpeed)(MotorData*, int);
    int (*GetSpeed)(MotorData*);
    void (*SetDirection)(MotorData*, DIRECTION);
    DIRECTION (*GetDirection)(MotorData*);
} MOTOR_CONTROL;

extern MotorControlData* MotorControlCtor(void);
extern void MotorControlXtor(MotorControlData*);
extern MOTOR_CONTROL* MotorControl;

The MotorControl class vtable pointer provides access to this class in the same way the Motor pointer did for the Motor class. We can use the same technique as before to initialize this pointer, but how do we provide both access to the base class methods and have it initialize our data structure?

Like the Motor class, the MotorControl class defines an instance of the vtable in its code file:

static const MOTOR_CONTROL
                             cMotorControl = {
  SetNewSpeed
};
MOTOR_CONTROL* MotorControl;

But wait! Where are the base class vectors listed in the typedef?

Step back and think about what is required in a derived class. You don't want the ROM overhead of same name functions in the derived class that just pass execution on to the base class. What you do want is for the derived class to be able to call the base class and have it initialize both the derived class's vtable and its base class data, and from then on just provide its methods for use until destructor time.

Harking back to the Motor class constructor, I called a macro to construct a copy of the vtable instance in RAM. If I use the same macro for the derived class, what happens?

CREATE_VTABLE(MotorControl, 
        cMotorControl, MOTOR_CONTROL);

Because it uses the size of the MOTOR_CONTROL structure, it copies the SetNewSpeed vector and leaves empty space for the other five vectors. If you now pass to the base class a pointer to where the first base class method should be in the MotorControl vtable, it can simply copy the addresses of these methods one after another back into the derived class's vtable. In the MotorControl example, the first base class method is InitPort, and if you take its address and pass it to the base class, the mechanism is rather simple. In the derived class:

void (**v)() = &(MotorControl->InitPort);

and in the base class:

  *v++ = InitPort;
  *v++ = SetSpeed;
  etc.

You also need the base class to initialize the base class data in the derived class. These requirements mean that, for the base class to initialize the derived class, you must pass to the base class constructor a pointer to the data structure in the derived class and a pointer to the first of the base class vectors in the derived class RAM vtable. Example 5, a revised Motor constructor to do just that, fills out the derived class RAM vtable with pointers to the base class methods.

Example 5: Revised Motor constructor.

MotorData* MotorCtor(void* data, 
                       void(**v)(void)
{
  MotorData* d;
  if (data)
  {
      // This is the base class for a 
      // defined class. The caller has
      // defined both their own 
      // data structure and vtable.
      d = data;
      if (v)
      {
          *v++ = InitPort;
          *v++ = SetSpeed;
          *v++ = GetSpeed;
          *v++ = SetDirection;
          *v++ = GetDirection;
      }
  }
  else
  {
      // This is a standalone 
      // class, and both the 
      // data structure and 
      // vtable must be created here.
      d = Allocate(sizeof(MotorData));
      if (InstCount++ == 0)
      {
        CREATE_VTABLE(Motor, 
                     cMotor, MOTOR);
      }
  }

  // Initialize the class data.
  d->mPort = NULL;
  d->mSpeed = 0;
  d->mDirection = DR_UNKNOWN;

  return d;
}

The derived class data pointer is assigned to the local pointer and subsequently the constructor initializes the data back in the derived class. On the other hand, passing in a couple of nulls sets up a standalone class. (This demonstration constructor is configured to build both derived classes and instances of itself; a rare situation in practice, and a true base class would leave out the standalone code.)

If you now call the MotorControl SetSpeed method, it directly executes that method in the base class.

The derived class can override a base class method by overwriting the associated vector in the vtable; for example, if the derived class wanted to override the base class SetDirection method. Then, after the vtable has been built, it could replace that method with a statement such as:

MotorControl->SetDirection = MySetDirection;

If access to the base class version is still required, you can declare a static function pointer for saving the base class pointer prior to the replacement. Example 6 is the example derived class constructor.

Example 6: Derived class constructor.

MotorControlData* MotorControlCtor(void)
{
    MotorControlData* d;

    d = Allocate(sizeof(MotorControlData));
    d->mAccelRate = 0;

    if (InstCount++ == 0)
    {
        CREATE_VTABLE(MotorControl, cMotorControl, MOTOR_CONTROL);
        MotorCtor(d, &MotorControl->InitPort);
    }
    else
    {
        MotorCtor(d, NULL);
    }

    return d;
}

Because in C_Classes the base class for a derived class contains nothing other than the code, the same base class can support a number of derived classes in the one application. In the example I present here, as well as this derived class that provides controlled acceleration for one motor, another derived class could provide pulse width control for a motor attached to a peristaltic pump, with the control input parameter being liters-per-hour.

Motor.h and motor.c are the header and code files (available here) for the now modified Motor base class, while MotorControl.h and MotorControl.c show the new MotorControl class. MotorVtable.h is the Motor base class vtable definition in a form that can be included in both the base class itself and any derived classes. Using this technique means there are only two places the programmer must ensure correspondence—the class definition and its instance in the class code file (although it does mean the compiler gives incompatible types warnings). Main.c (available electronically) is a demonstration main program showing examples of using these classes. The ports in the microprocessor are simulated as global integers. It is instructive to create a project in something like Visual C and step through the entire program.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.