Classes within Classes - Initialiser Lists

There's a problem that crops up over and over when people write object oriented (OO) code for Arduino. When you try to instantiate an object of a class which in turn instantiates an object of another class; things go wildly wrong. The error messages you get are cryptic and, unless you're a fair C++ programmer, useless.

An example of when this might be required is this. I'm creating a GPS class to interpret data that comes from a GPS device. To my mind, the GPS object should instantiate its own serial object so it can receive data and do what it wants with it. It shouldn't be the job of the main sketch to go get the serial data and pass it to the GPS object - as would happen if the main sketch instantiated two objects, one for serial, one for GPS. I'm going to do a walk-through on how to get the main sketch to instantiate an object of a class, and as that object is being created, let the new object create its own object of a different class.

Have a look at my last post for the code for ClassSketchPlus. Create a new project with this code. Once you've created the project close the Arduino IDE and rename the TestClass.h and TestClass.cpp files to First.h and First.cpp. Copy First.h and First.cpp and rename the copies Second.h and Second.cpp. When you re-open the project you'll need to do some search-and-replace operations:
  • In the main sketch file, replace TestClass with First
  • In both First files, replace TestClass with First
  • In both Second files, replace TestClass with Second
In the Arduino IDE you should now see these tabs.
If you compile, sorry "Verify", everything should be sweet. But the class Second is doing nothing. It's not included by either the PDE file or First.h and so has no effect. So let's add it to the First.h file - add it in First.h above the class First line:
#include "Second.h"
This too should compile fine. OK, so how do we instantiate an object of the Second class? Let's add a private member for that. Remember this is just a reference that can refer to an object. Adding this line doesn't create an object anywhere; the reference points to nothing. The First.h file now looks like this:
#ifndef First_h
#define First_h

#include "Second.h" // ADDED!

class First
{
public:
First(int newNumber);
int Number();
private:
int itsNumber;
Second widget; // ADDED!
};

#endif
Now if we try to compile, we've stuffed it.
First.cpp: In constructor 'First::First(int)':
First.cpp:2: error: no matching function for call to 'Second::Second()'
/Second.h:7: note: candidates are: Second::Second(int)
/Second.h:5: note: Second::Second(const Second&)
Great! So what's that mean? I could try to explain, but we know we haven't created an object for widget to refer to. So let's try to do it. I'm going to show my ignorance here, but I went to First.cpp and added lines to the constructor to try to create an object for widget.
#include "First.h"

First::First(int newNumber)
{
itsNumber = newNumber;
Second widget(7); // Didn't work.
// widget = Second(7); // Didn't work either.
}

int First::Number()
{
return itsNumber;
}
Remember the New C++ keyword doesn't exist in Arduino unless you implement it yourself. So widget = New Second(9); isn't going to work. The secret sauce is an initialiser list, or initializer list of you're American! :)

In the first line of the implementation for the First class' constructor (in First.cpp), append a colon and a constructor call for widget, supplying any required parameters. Like this:
First::First(int newNumber) : widget(9)
{
itsNumber = newNumber;
}
Compile it. You like, yes?

Of course this can be extended to any number of classes. Each initialiser list can instantiate multiple objects. Each object being instantiated can instantiate its own objects. I plan to take this to the extreme: the main sketch will instantiate a LifeClock object which will create multiple objects each creating multiple objects. I've mapped out about 5 levels deep in my planning.

A number of improvements are beckoning. First, a constructor for the First class should have parameters that are immediately passed (in the initialiser list) to the Second object. This is instead of using hard-coded numbers. Also, note this method does not require that both First and Second header files be included at the main sketch level. If this were necessary as you "nested" your classes you would have to keep track of the dependencies and ensure your include list was correct.

Taking a short-cut to the next level, I've developed a quick test program that's an evolution of what we've seen so far. Download the code here. It shows classes First and Second in action and passing parameters between constructors using an initialiser list on the First constructor.

Finally, I want to go back and explain the error message I skipped past earlier. The first two lines of the message say that the constructor in the First class (which is called when the line First thing(6); is reached in the main sketch) can't find a constructor in the Second class matching the signature Second::Second(). Note that the Second constructor has no parameters. What the? Why is it looking for a constructor for Second? You'll find that no matter what you put inside First's constructor, this error will remain. It seems that as soon as the constructor for First is reached, a default constructor for Second is called unless an initialiser list says otherwise. The initialiser list we put in calls the correct constructor for Second (the one with an integer parameter) and provides the value for that parameter. So all is well.

If an initialiser list is not used, a "no parameters constructor" is assumed. Thus there are two instances where initialiser lists are not required to create an object from another object. If your "second" class has a constructor with no parameters, it will be called anyway. If your "second" class has no constructors, then the implicit no-parameter constructor is called successfully anyway (it exists even if you don't code it). Of course if you code more than one constructor, be careful! Without an initialiser list, the no-parameters constructor will always be called regardless of your code inside the "first" class' constructor.

As I'm choosing to do my Arduino development in an OO fashion, you should see this all through my code as the project progresses. Look for further examples in my future posts.

That is all!

Comments

  1. Hi!

    Thank you so much for your explanations; they solved my problem.

    Now, I initialise both my GPS class and the software serial port it requires.

    Best regards,

    ReplyDelete
  2. Do you have your completed source for download? The link doesn't work any more.

    Thanks

    ReplyDelete
  3. Code sample based on this great article:
    http://arduino-class.googlecode.com/hg/3_CodeSamples/ClassesWithinClasses.zip

    ReplyDelete
  4. Hi Christian,
    your Code already helped me very much, but I got one question:

    you define your widget with a static
    First::First(int newNumber) : widget(9)

    how can I keep it variable like:
    First::First(int newNumber) : widget(int a, int b)

    do you know the answer? would be really thankfull for help!

    ReplyDelete
  5. Hi Jochen, It's been a while since I've played with this, so I'm a bit rusty. I think the answer is to have enough variables in your First constructor to populate the initialiser list. So I would try:

    First::First(int newNumber, int widgetA, int widgetB) : widget(widgetA, widgetB)

    Let me know if that works! Chris.

    ReplyDelete
    Replies
    1. Hi Christian,

      thank you for your quick response, it works perfect!
      You're doing a great job here.

      Delete
    2. Hi Christian,

      Thanks for the tutorial. It saved me a lot of head scratching.

      Cheers,

      Delete
  6. Thanks alot for this great article :) this will really help to organize my arduino projects!

    ReplyDelete
  7. Absolutely great article! thank you so much, it helped me a lot :) I use to program in Java and after getting this compile error I ended up by declaring al constructors without parameters and after instantiate objects I set properties. It works but ir ducks XD

    Your solution is perfect ;)

    ReplyDelete
  8. Great article, excellent writing style, (very understandable and no missing detail). You solved my identical problem after 3 minutes. I struggled for 3 days before finding the article.
    Thanks

    ReplyDelete
  9. Hi I'd like to add this to this, in case someone has run into the same issue as me...

    Don't try to define variables in your header file and then pass them to the constructor. In the header file you can only declare variables but not assign them any values. The compiler happily compiles without giving you any errors! Be careful!

    /*** THIS IS OK ***/

    // In First.cpp
    First::First(int newNumber) : widget(9)
    {
    itsNumber = newNumber;
    }



    /*** THIS IS NOT OK ***/

    // In First.h
    myInt = 9;

    // In First.cpp
    First::First(int newNumber) : widget(myInt)
    {
    itsNumber = newNumber;
    }


    This will compile but in reality the "widget" object will never get made.


    Hope this helps someone.

    Ibrahim

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. @Ibrahim, thank you very much for you tip.

      May I ask you a question ... on a stupid problem, how do you update a variable in subclass...
      I already opened a question on a forum : http://arduino.stackexchange.com/questions/32354/a-class-within-a-class-via-an-initialiser-lists-not-able-to-update-a-variable

      My idea is to use a
      1/ subclass : Debounce : to debounce the digital inputs/switches
      2/ mainclass : Calculator : which do some calculation depending on which input ...

      So the mainsketch updates via millis the subclass, depending on the conditions the subclass defines if it is a valid or unvalid input.
      In case it is valid, it has inform the Class Counter about this event/condition-state change.

      Thanks.

      Delete
  10. Thanks for this tutorial. If you want to initialize more than one object, you can expand the list with more initializations, separated by ,
    So First::(): widget1(1), widged2(2), ..etc.. {constructorcode}

    ReplyDelete
  11. @UnknownJune 26, 2016 at 9:12 PM
    https://github.com/droopy4u/ARDUINO
    (with three classes and two times used the same class)

    Correctly ...


    I opened a question on http://arduino.stackexchange.com/questions/32354/a-class-within-a-class-via-an-initialiser-lists
    with the title : A class within a class - via an Initialiser Lists

    Because ... I'm not able to update a variable?
    Maybe you had/have the same problem?

    ReplyDelete
  12. That's insane. You solved my problem with the silliest syntax ever. Guess I've been so spoiled by Python. :-)

    ReplyDelete

Post a Comment