C++ help - Using a base class

I need the help of C++ gurus out there. I am trying to define a base class for the RGBPongClock clock face class. I want to use that base class to create an array of pointers which I will later populate with the instantiated class address. The idea is to create a list of (pointers to) clock “face” objects that all use the same base class but redefine the virtual functions based on each clock.

I have created a base class as follows:

// face_class.h

#ifndef _FACE_CLASS_H
#define _FACE_CLASS_H

#include "application.h"

class FaceClass
{
  public:
	FaceClass();
	virtual ~FaceClass();
	
	// These must be defined by the subclass
	virtual void begin(void);				// Reset face for running
    virtual void run(void);					// Update the face animation
	virtual int isComplete(void);			// True if animation completed, false if not, -1 if not applicable
	
//  private:
//	byte hours, mins;
};

#endif
face_class.cpp

#include "face_class.h"


FaceClass::FaceClass() { }
FaceClass::~FaceClass() { }

void FaceClass::begin(void) { }

void FaceClass::run(void) { }

int FaceClass::isComplete(void) {  return -1;}

I then instantiate the class object pointer using FaceClass * faces[MAX_FACES]; and populate an instantiated object’s pointer using faces[0] = new face_normal(); whre face_normal is declared as class face_normal : public FaceClass

When I compile this code using the latest develop, I get:

../../../build/target/user/platform-6-m/applications/RGBPongClockV2/\libuser.a(R
GBPongClock.o): In function `setup':
C:\SPARK\ParticleBeta\firmware\user/applications/RGBPongClockV2/RGBPongClock.cpp
:309: undefined reference to `vtable for face_normal'

Line 309 doesn’t seem related whatsoever.

I suspect I’m missing something here. Any help is greatly appreciated :blush:

ping @mdma

Paul, you of all people should know the key to a good bug report is the details! :stuck_out_tongue_winking_eye: What error message are you getting?

@mdma, OMG!!! I can’t believe I cut&paste the wrong thing!!! I edited the last paragraph with the correct message. :flushed:

I'm still not seeing the error message

Knowing specifically which linker error it is will hopefully help troubleshoot - I don't see any problems with what you've posted so far.

@mdma, I updated the previous post. Perhaps you need to do a refresh?

Got it thanks. Are you sure all virtual methods have corresponding definitions? A missing virtual method will cause this kind of linker error. http://stackoverflow.com/questions/2182738/debugging-vtable-linker-errors-in-gcc

As an abstract base class, it’s probably a good idea to make these virtual methods pure:

virtual void run()=0;

Then any attempt to instantiate a subclass with missing definitions will result in a much more descriptive compiler error telling you which virtual method isn’t defined.

2 Likes

@mdma, I tried making them pure virtual and still got the same error. However, I’ll give it a shot again and report back. :wink:

Is this one of those situations where you supplied a default constructor, so the compiler does not create one for you, but your default constructor is empty?

What happens if you remove this from the CPP file?

FaceClass::FaceClass() { }

The error is for the face_normal class so check that any virtual methods you declare there in the header also have function bodies.

I rarely separate C++ classes in to separate .h and .cpp files since it’s often not necessary, avoiding this class of error entirely, plus you then get the added bonus of possible inlining for non-virtual methods.

@mdma, I am trying to create an array of class pointers that gets populated based on compile flags. Not all classes are included all the time. This is why I felt making a base class to create the pointer array was necessary. Is there a better way to do this?

@bko, I tried combinations with and without constructor / destructor with no luck. I finally narrowed it down to faces[0] = new face_normal();

It seems this line does not work as expected.

@mdma, I’m at a loss with this array of class pointers thing. It seems that creating an array of base class pointers doesn’t work (despite everything I read). What is the best way to build an array of pointers to a set of similar objects (same functions) so I can call the objects functions without having to specify the specific object (ie via its pointer)?

What you’re doing makes sense. Did you check all the virtual methods are declared in your derived class face_normal class?

@mdma, yup. I now split the object creation and array assignment into two parts
(globally) face_normal FaceNormal();

(in setup()) faces[0] = &FaceNormal;

Now I get this error:

applications/RGBPongClockV2/RGBPongClock.cpp:239:11: error: cannot convert 'face
_normal (*)()' to 'FaceClass*' in assignment
  faces[0] = &FaceNormal;

The compiler considers FaceNormal to be referring to a function rather than an instance. Without seeing all the code I would only be giving wild guesses.

Just to double check that the code is

faces[0] = &FaceNormal;

and not

faces[0] = &FaceNormal();

or similar? The function type is being introduced somewhere.

1 Like

@mdma, yup, that’s how I have it and I instantiate using face_normal FaceNormal();

The constructor is an empty function and not declared virtual.

Just as well, constructors cannot be virtual by definition! :wink:

I don't think I will be able to help without seeing the full source. What you're trying to do is perfectly valid - pointers to subclasses using virtual methods is a common pattern, but there is of course some error that's tripping you up, but it's not in the snippets you've provided.

@mdma, I’ll post the code to the RGBPongClockV2 repo and send you a link. Thanks!

@mdma, the repo is HERE. The files of interest are:

Main program - create pointer array and stuff with class pointers
RGBPongClockV2.cpp

Class for “normal” clock face using base class “face_class”:
face_normal.h
face_normal.cpp

Base class for all clock faces:
face_class.h
face_class.cpp

:smile:

Here’s some things to try:

missing semi-colon after constructor:

missing multi-include guard - add #pragma once to the top of the face_normal.h file

In face_normal.cpp the whole content is guarded by a #ifdef FACE_NORMAL, which is false. (That symbol is defined inline in the main cpp file, but that only defines it for the duration of that file.)
Best to move these configuration #defines out into a separate file and include these in each file that needs to be configured.

Finally change:

face_normal FaceNormal();

to

face_normal FaceNormal;

The former is a function declaration, while the latter is an instance declaration.

Those changes should get you moving again.

Also for your sanity, I suggest sticking to a single naming convention for instances and classes. Currently there’s FaceClass but then subclasses face_normal. Classes are typically mixed case by convention, so FaceNormal would be the class name, and faceNormal or face_normal the instance name.

2 Likes