Type cast of pointers?

Hi, I'm working on a software to control a multi function hardware (signal generator, frequency counter, state analyzer) with a touch display. The software shall run on a Due.

  • Data and features of the functions are defined by classes/objects, i.e. one class per function
  • For every function there are several input/output boxes on the display, i.e. they look differently. To display them there is a draw() method - different for every function.
  • Only one of the functions is active at a time

For finding a good solution for the control of the functions I wrote a small test:

  • There is a base class, MuFu_Base, with methods init() and draw().
  • There is a first function class, MN, inheriting from MuFu_Base, with methods draw() and process()
  • There is a second function class, SG, inheriting from MuFu_Base, also with methods draw() and process()
  • The methods draw() and process() have different contents, of course

To call the methods I want to use a single pointer with type casting to point to MuFu_Base or MN or SG.
And here is my problem how to do this. Here is the sketch for my tests:

#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"     // erbt ueber Adafruit_SPITFT von Adafruit_GFX
#include "MyClasses.h"

#define TFT_DC        12
#define TFT_CS        71
#define TFT_RST       70

// Initialize tft display
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

MuFu_Base *ptNFunc = NULL;              // trial 1/3
// void *ptNFunc = NULL;                // trial 2
void setup() {
  ptNFunc = new(MuFu_Base);              // trial 1/3
  // ptNFunc = (MuFu_Base *)(new(MuFu_Base));  // trial 2
  
  ptNFunc->init(50, 10, 50, 20);
  ptNFunc->draw(&tft);
  delete(ptNFunc);

  ptNFunc = (MN*)(new(MN));
  // ptNFunc = dynamic_cast<MN *>(MuFu_Base);   // trial 3
  ptNFunc->init(50, 40, 50, 20);
  ptNFunc->draw(&tft);
  ptNFunc->process(&tft);     // line 32
  delete(ptNFunc);

  ptNFunc = (SG*)(new(SG));
  ptNFunc->init(50, 70, 50, 20);
  ptNFunc->draw(&tft);
  ptNFunc->process(&tft);     // line 38
}

void loop() {
  // put your main code here, to run repeatedly:

}

Declarations and definitions of the classes are attached.

I tested three versions how to define and use the single pointer ptNFunc.
trial 1:
see sketch above.
compiler error (shortened):

In function 'void setup()':
...\MultiFunction.ino:32:12: error: 'class MuFu_Base' has no member named 'process'
ptNFunc->process(&tft);
^
...\MultiFunction.ino:38:12: error: 'class MuFu_Base' has no member named 'process'
ptNFunc->process(&tft);

trial 2, lines with comments "trial 2" activated, others commented out:

// MuFu_Base *ptNFunc = NULL;              // trial 1/3
void *ptNFunc = NULL;                // trial 2
void setup() {
  // ptNFunc = new(MuFu_Base);              // trial 1
  ptNFunc = (MuFu_Base *)(new(MuFu_Base));  // trial 2
  
  ptNFunc->init(50, 10, 50, 20);        // line 24
  ptNFunc->draw(&tft);                  // line 25

compiler errors (shortened):

In function 'void setup()':
...\MultiFunction.ino:24:10: error: 'void*' is not a pointer-to-object type
ptNFunc->init(50, 10, 50, 20);
^
...\MultiFunction.ino:25:10: error: 'void*' is not a pointer-to-object type
ptNFunc->draw(&tft);
^
... a lot more errors

trial 3, with dynamic_cast:

MuFu_Base *ptNFunc = NULL;              // trial 1/3
// void *ptNFunc = NULL;                // trial 2
void setup() {
 ptNFunc = new(MuFu_Base);              // trial 1/3
 // ptNFunc = (MuFu_Base *)(new(MuFu_Base));  // trial 2
 
 ptNFunc->init(50, 10, 50, 20);
 ptNFunc->draw(&tft);
 delete(ptNFunc);

 // ptNFunc = (MN*)(new(MN));
 ptNFunc = dynamic_cast<MN *>(MuFu_Base);   // trial 3
 ptNFunc->init(50, 40, 50, 20);

compiler error:

In function 'void setup()':
...\MultiFunction.ino:29:41: error: expected primary-expression before ')' token
ptNFunc = dynamic_cast<MN *>(MuFu_Base); // trial 3
^
... a lot more errors

What is wrong with my type castings?

MyClasses.cpp (1.4 KB)
MyClasses.h (1.1 KB)

Why not start with a google of function pointers? I am sure you will get lot's of hits.

True. The MuFu_Base class has no member named 'process'.

Again true. A pointer to void doesn't magically store what object its value originally pointed to.

Think about this: you have a MuFu_Base object. What can it possibly know about any class derived from it, or members of that class? And therefore, what good can come of casting a pointer to a base class object to a derived class?

It's not a matter of function pointers but of a single pointer pointing to several objects inherited from a base object.

class Base {
  virtual void func1();
  virtual void func2();
};

class T2: public Base {
  void func2();  // inherited but different from Base::func2()
  void func3();  // method only for T2
};

class T3: public Base {
  void func2();  // inherited but different from Base::func2()
  void func3();  // method only for T3
};

Now I need a pointer once pointing to a Base object, later to a T2 object and later to a T3 object.

well, I expected that the pointer to the derived class can call (pointer->func()) all methods of the base class and all additional methods of the derived class, isn't it?

You have declared an object of the base class.

You can change a pointer to that object to whatever derived class you like.

It will not magically transform the base class object to an object of the derived class or add its attendant methods.

1 Like

Aside from other problems with your code, dynamic_cast has a high run-time cost and may not be implemented on some Arduino boards. Why not use regular polymorphism? Have virtual functions in the base class and override them in the derived classes.

2 Likes

Other languages like Python and Go do not require parentheses following control flow like if and for. You still use them to explicitly change operator precedence. I've wondered if you could get away with a language that doesn't allow such usage: some expressions would then require multiple statements. But it would prevent beginners from learning the bad habit of just blindly adding parentheses in hope that "it works". C++ is particularly bad about this.

  • We're using (relatively) modern C++, so use nullptr instead of NULL unless you understand why you'd need the latter.
  • You don't need either
    • Global variables are initialized to zero
    • Nothing is going to secretly use your variable before it is initialized in setup
  • new does not require parentheses. They're used when calling a constructor, but you may be better off with brace initialization
    auto ip0 = new IPAddress;
    auto ip1 = new IPAddress(111, 222, 333, 44);  // accepted with warning
    auto ip2 = new IPAddress{ 111, 222, 333, 44 };  // error
    

That last one is an abomination (with entirely trivial stakes)

  • We're using C++. If you're going to cast, use the appropriate _cast
  • You don't need a cast. new returns a pointer. No point (pun intended) casting to what it already is
  • The problem is that you're then assigning to void *, which is always allowed: converting a pointer-to-object to "just a pointer". So then -> doesn't work

The argument of a _cast is a value -- a primary-expression -- like a pointer in this case, not a type. And again, assigning the result of the cast to a variable of a simpler type means the cast is lost.

The code is compiled once, long before it is run. Even if you were to say

Base *foo = new Derived;
foo->whatever();
foo = new Other_Derived;
foo->whatever();

that function has to be part of the Base class, because that's what you told the compiler to enforce. Each call could do something different, if one has an override and the other does not, or if they both have a different override.

Is my understanding of your last sentence OK, that there is no way to make ptNFunc point to a derived object and call methods of the derived object not contained in the base object?

As phrased, the answer is no. But I'm guessing you want to do something like

struct Animal {
  virtual void breathe() {
    Serial.println(millis());
  }
};
struct Fish : public Animal {
  void breathe() override {
    Serial.println("gills");
  }
  void swim() {
    Serial.println("swim");
  }
} fish;
struct Bird : public Animal {
  void fly() {
    Serial.println("fly");
  }
} bird;

void setup() {
  Serial.begin(115200);
}

void loop() {
  Animal *a;
  if (random(2)) {
    a = &fish;
  } else {
    a = &bird;
  }
  a->breathe();
  if (auto f = dynamic_cast<Fish *>(a)) f->swim();
  if (auto b = dynamic_cast<Bird *>(a)) b->fly();
  delay(750);
}

Do you see the difference?

That sketch above compiles only if the -fno-rtti compiler flag (in the platform.txt for most boards) is removed. As mentioned, dynamic_cast also incurs runtime overhead. Depending on exactly what the extra functions in the derived objects do, it might be better to have do-nothing stubs in the base class, and blindly call those. And/or have those stubs return whether they actually do anything. Or maybe have a "dispatcher" function. Several non-RTTI options.

OK, I read a lot about this pointer topic. dynamic_cast could solve it with the mentioned implications. But I decided for the easier way of "do-nothing stubs" in the base class.
Thank you very much for your patience and valuable infos.

You probably don’t need to cast anything at least for init and display, just leverage pure virtual functions.

here is an example

class MuFu_Base {
public:
  virtual void init()    = 0;
  virtual void draw()    = 0;
  virtual void process() = 0;
};

class MN : public MuFu_Base {
public:
  void init() override    { Serial.println("MN init"); }
  void draw() override    { Serial.println("MN draw"); }
  void process() override { Serial.println("MN process"); }
};

class SG : public MuFu_Base {
public:
  void init() override   { Serial.println("SG init"); }
  void draw() override    { Serial.println("SG draw"); }
  void process() override { Serial.println("SG process"); }
};

MN mn;
SG sg;

MuFu_Base* devices[] = { &mn, &sg };
const byte numDevices = sizeof devices / sizeof *devices;
byte currentIndex = 0; // was index see further comments 

void setup() {
  Serial.begin(115200);
  mn.init();
  sg.init();
}

void loop() {
  MuFu_Base* d = devices[currentIndex];
  d->draw();
  d->process();
  currentIndex = (currentIndex + 1) % numDevices;
  delay(3000);
}

you instantiate your instruments separately (mn and sg)
here I store them into an array of pointers of the base class
every 3s in the loop I go to the next entry (and cycle) in the array and call pure virtual functions on the current pointer in the array.
because the functions are pure virtual, despite the fact that the array is of the base class, you can see in the serial monitor that actually the correct method is called in the device instance.

1 Like

This goes boom on non-AVR targets, index() already exists.
It would work as a static variable inside loop().

NAME
       index, rindex - locate character in string

LIBRARY
       Standard C library (libc, -lc)

SYNOPSIS
       #include <strings.h>

       [[deprecated]] char *index(const char *s, int c);
       [[deprecated]] char *rindex(const char *s, int c);

DESCRIPTION
       index() is identical to strchr(3).

       rindex() is identical to strrchr(3).

       Use strchr(3) and strrchr(3) instead of these functions.

STANDARDS
       None.

HISTORY
       4.3BSD; marked as LEGACY in POSIX.1-2001.  Removed in POSIX.1-2008,
       recommending strchr(3) and strrchr(3) instead.

SEE ALSO
       strchr(3), strrchr(3)
1 Like

Good point indeed !
Fixing it - renaming it currentIndex

Back to my Post #7 three days ago.

yes indeed - for some reason I had missed it. Hopefully the code example will help OP if he did not understand what you meant with polymorphism.