-2

Let me preface this by saying I'm coming from a Java background, so please forgive me if I've made some stupid mistake here....

I'm writing a C++ library that I would like to make compatible with both Arduino as well as the RaspberryPi. Most of the code is just clean, standard C++, but a few functions are hardware-specific (GPIO calls, for instance). Being from a Java background, I thought, I'll just create a HAL interface that defines those hardware-specific functions, and then I can create implementations of that interface for the RPi and Arduino, only to find out that interfaces are not a thing in C++ and you have to use virtual methods instead. Please see the bottom of my question for my source files.

However, when I went to actually use this, I discovered something quite strange: using the HAL on the heap compiles, but using it on the stack does not

//THIS DOES NOT COMPILE
MyLibraryHAL hal = HAL_RaspberryPi();
hal.someHwSpecificFunction(65);

//BUT THIS DOES
MyLibraryHAL* hal = new HAL_RaspberryPi();
hal->someHwSpecificFunction(65);

The error is:
src.ino:67: undefined reference to MyLibraryHAL::someHwSpecificFunction(unsigned char).

Why is this? Is this just a feature of how virtual methods work? Or is there some concept I'm missing here?


MyLibraryHAL.h

#include <stdint.h>

#ifndef MyLibraryHAL_h
#define MyLibraryHAL_h

class MyLibraryHAL
{
    public:
        virtual void someHwSpecificFunction(uint8_t param);
};

#endif

HAL_Arduino.h

#include <stdint.h>
#include "MyLibraryHAL.h"

#ifndef HAL_Arduino_h
#define HAL_Arduino_h

class HAL_Arduino : public MyLibraryHAL
{
    public:
        void someHwSpecificFunction(uint8_t param);
};

#endif

HAL_Arduino.cpp

#include <stdint.h>
#include "HAL_Arduino.h"

void HAL_Arduino::someHwSpecificFunction(uint8_t param)
{
    //do things
}

HAL_RaspberryPi.h

#include <stdint.h>
#include "MyLibraryHAL.h"

#ifndef HAL_RaspberryPi_h
#define HAL_RapsberryPi_h

class HAL_RaspberryPi : public MyLibraryHAL
{
    public:
        void someHwSpecificFunction(uint8_t param);
};

#endif

HAL_RaspberryPi.cpp

#include <stdint.h>
#include "HAL_RaspberryPi.h"

void HAL_RaspberryPi::someHwSpecificFunction(uint8_t param)
{
    //do things
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/193485/discussion-on-question-by-android-dev-can-instantiate-a-virtual-object-on-the-he). – Bhargav Rao May 16 '19 at 17:39
  • Possible duplicate of [Virtual Functions Object Slicing](https://stackoverflow.com/questions/3479712/) – Remy Lebeau May 16 '19 at 20:34

3 Answers3

1

Without pointer, you copy a HAL_RaspberryPi to a MyLibraryHAL instance, then call the method on the superclass (which is not defined).

So yes, use a pointer, but you should use std::shared_ptr or std::unique_ptr instead of raw pointers.

B Wakar
  • 44
  • 1
  • You mean to say that the superclass need to define that method? – Hemil May 16 '19 at 15:31
  • It has to be defined somewhere, either by the parent class (not PURE) or by the sub-class in the case of PURE func()=0 style methods. If you do not define an implementation and try to instantiate in the heap, you'll get runtime surprises. – Luis Dipak May 16 '19 at 15:42
  • Hope this helps, @Android Dev – Luis Dipak May 16 '19 at 15:45
  • 1
    Thank you for actually explaining why my snippet didn't compile instead of just telling me to read a C++ book (which, I freely admit, would be a good thing to do) – You'reAGitForNotUsingGit May 16 '19 at 15:52
  • Note, AFAIK, `unique_ptr` and `shared_ptr` aren't part of the Arduino toolchain, but you can do `Tools\Manage libraries...` and add the `ArduinoSTL` library to your toolchain. Then do: `Sketch\Include library` and find the `ArduinoSTL` almost at the bottom of the list. Then do `#include ` and you now have access to `std::auto_ptr` which is very old, but better than nothing, so you can do `std::auto_ptr hal( new HAL_RaspberryPi() );` and have your `HAL_RaspberryPi` instance automatically destructed when the smartpointer goes out of scope. – Ted Lyngmo May 16 '19 at 16:14
1

You /can/ do this by making your stack variable a reference:

Base& base = Derived(args);

But realistically it doesn't do you a lot of favours, as the reference can only be assigned where it is declared, so the best you can do to avoid construction is:

Base& base = Do1 ? Derived1(args) : Derived2(args);

You have to be very wary of the lifetime extension rules for references. Make of this what you will:

https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
1

The statement:

MyLibraryHAL hal = HAL_RaspberryPi();

slices the HAL_RaspberryPi object, so there is only a MyLibraryHAL object remaining. See What is object slicing?.

You need a pointer or a reference in order to make polymorphic calls at runtime. That is why your heap example works. See Why doesn't polymorphism work without pointers/references?. You can use polymorphism with objects that are instantiated on the stack, but you still need a pointer/reference in order to make the calls, eg:

HAL_RaspberryPi pi;
MyLibraryHAL &hal = pi;
hal.someHwSpecificFunction(65);
HAL_RaspberryPi pi;
MyLibraryHAL *hal = &pi;
hal->someHwSpecificFunction(65);

As for the linker's "undefined reference" error, you did not actually implement the someHwSpecificFunction() method in the MyLibraryHAL class, you merely declared it, thus the error when trying to call it on a MyLibraryHAL object. If you don't want to provide a default implementation, you can declare the method as a "pure abstract" method:

class MyLibraryHAL {
public:
    virtual void someHwSpecificFunction(uint8_t param) = 0;
};

Pure abstract methods must be overriden in descendant classes. A class that contains only pure abstract methods and nothing else is the closest thing you can get to an "interface" in other languages.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770