0

I have this base class:

// put the display in a macro on a .h file for less headache.
class Gadget {
  protected:
    int x, y;
    U8GLIB * u8g;

    virtual int  f_focus()  {return 0;};
    virtual int  f_blur()   {return 0;};
    virtual void f_draw()   {};
    virtual void f_select() {};


  public:
    Gadget(U8GLIB * u8g, int x, int y) :
      u8g(u8g),
      x(x),
      y(y)
    {
      Serial.println(F("Gadget(U8GLIB * u8g, int x, int y)"));
    };

    Gadget() {
      Serial.println(F("Gadget()"));
    };

    int     focus(){return f_focus();};
    int     blur(){return f_blur();};
    void    draw(){f_draw();};
    void    operator()(){f_select();};
};

And this derived class:

class WakeUp :
  public Gadget
{
  public:
    WakeUp(U8GLIB * u8g) :
      Gadget(u8g, 0, 0)
    {
      Serial.println(F("WakeUp(U8GLIB * u8g)"));
    };

};

Then I instantiate the WakeUp class inside an array like this:

Gadget gadgets[1] = {
  WakeUp(&u8g)
};

Then I try to access this member like this:

void focus() {
  Serial.println(gadgets[0].focus());
}  

It is supposed to display 0. However it is displaying -64. Even if I override the f_focus() method on WakeUp class. If I remove the virtual specifier from f_focus() it works fine, displaying 0, but I will not be able to access the derived class implementation of this method. I wish to understand what is causing this strange behavior and what can I do to avoid it.

EDIT:

The function runs fine if I call it from the Gadget Constructor.

user0042
  • 7,917
  • 3
  • 24
  • 39
Luiz Menezes
  • 749
  • 7
  • 16
  • Don't you have to convert the return value to string before passing it to `Serial.println`? – The Quantum Physicist Sep 18 '17 at 14:54
  • No. It accepts integer values. – Luiz Menezes Sep 18 '17 at 14:55
  • 1
    An array of `Gadget`s contains `Gadget`s, not widgets. If you want to store instances of classes derived from `Gadget`, you need an array of `std::unique_ptr`s. – Quentin Sep 18 '17 at 14:56
  • @LuizMenezes While what you're saying may be right, it's also ambiguously dangeorous. `std::string`'s standard constructor also takes an integer and constructs an empty string with the given integer size. Are you aware of this? – The Quantum Physicist Sep 18 '17 at 14:56
  • Of interest: [c++ - What is object slicing?](https://stackoverflow.com/questions/274626/what-is-object-slicing) – lcs Sep 18 '17 at 14:57
  • 2
    I have no stdlib on arduino. So my gadgets will have to be stored in an array. Also Serial.println accepts integers. The documentation is clear about this. https://www.arduino.cc/en/Serial/Println – Luiz Menezes Sep 18 '17 at 14:58
  • @LuizMenezes Again, do you understand what ambiguity in function definitions is? Actually it's stupid to accept a string OR an integer, I'm a little surprised if it's true. It's AMBIGUOUS. If it works, good for you. But this is the kind of situation you want to avoid for future compatibility. Please understand that this is advice. So you're free to take it or leave it. – The Quantum Physicist Sep 18 '17 at 15:01
  • Looks like you corrupted that memory somehow, looking into your code there is no surprise. But you do not provide enough information to diagnose it. – Slava Sep 18 '17 at 15:01
  • I think I got it... I am calling focus before the constructor for Gadget is called. Then I will get all manner of strange behavior... – Luiz Menezes Sep 18 '17 at 15:02
  • There is no ambiguity. It is called overloading. And Slava is right. In order to diagnose I wold have to look into the construction sequence, and that would take a lot of code to post here. – Luiz Menezes Sep 18 '17 at 15:05
  • Your problem is with object slicing. That's a given. But I'm talking about something else that doesn't seem to interest you as you seem super-confident that what you're doing is right. If someone else has the patience to explain it, please go ahead. I'm done. Have a nice day :-) – The Quantum Physicist Sep 18 '17 at 15:08
  • @TheQuantumPhysicist No [`std::string` standard constructor](http://en.cppreference.com/w/cpp/string/basic_string/basic_string) takes an integer as its first and *only* argument, so it can't be constructed from an integer value. – Some programmer dude Sep 18 '17 at 15:12
  • @Someprogrammerdude That's surprising, as I do that with vectors all the time. I thought they shared the same interface (from definition 2 of that cppreference link). Apparently it takes a char too to work. I stand corrected. – The Quantum Physicist Sep 18 '17 at 15:16
  • 1
    @TheQuantumPhysicist While I appreciate your patience and the aditional info about object slicing, something I did'nt know about until today, you are missing some crucial information on your advice about Serial.println. It is an arduino core function that prints it argument on serial port. It accepts String (not std::string because arduino have no support to stdlib), integer, float and char*. – Luiz Menezes Sep 18 '17 at 15:17
  • @LuizMenezes That's useful to know, too. Thanks. I thought it used `std::string` and that was my contention (until having been corrected by Someprogrammerdude). – The Quantum Physicist Sep 18 '17 at 15:19

1 Answers1

2

You're slicing your WakeUp object.

You essentially have the following:

Gadget g = WakeUp(...);

What this code does is the following:

  1. Construct a WakeUp object.
  2. Call Gadget(const Gadget& other) with the base from the WakeUp object.
  3. Destroy the temporary WakeUp object, leaving only the copy of the Gadget base.

In order to avoid this, you need to create an array of pointers (this is better if they are smart pointers).

Gadget* gadgets[1] = { new WakeUp(&u8g) }; // If you choose this method, you need to call 
                                           // delete gadget[0] or you will leak memory.

Using a pointer will correctly preserve the Gadget and WakeUp instances instead of slicing them away.

With smart pointers:

std::shared_ptr<Gadget> gadgets[1] = { std::make_shared<WakeUp>(&u8g) };
lcs
  • 4,227
  • 17
  • 36