Classes and objects: how many and which file types do I actually need to use them?

How the IDE organizes things

First thing, this is how the IDE organizes your "sketch":

  • The main .ino file is the one of the same name as the folder it is in. So, for foobar.ino in foobar folder - the main file is foobar.ino.
  • Any other .ino files in that folder are concatenated together, in alphabetic order, at the end of the main file (regardless of where the main file is, alphabetically).
  • This concatenated file becomes a .cpp file (eg. foobar.cpp) - it is placed in a temporary compilation folder.
  • The preprocessor "helpfully" generates function prototypes for functions it finds in that file.
  • The main file is scanned for #include <libraryname> directives. This triggers the IDE to also copy all relevant files from each (mentioned) library into the temporary folder, and generate instructions to compile them.
  • Any .c, .cpp or .asm files in the sketch folder are added to the build process as separate compilation units (that is, they are compiled in the usual way as separate files)
  • Any .h files are also copied into the temporary compilation folder, so they can be referred to by your .c or .cpp files.
  • The compiler adds into the build process standard files (like main.cpp)
  • The build process then compiles all the above files into object files.
  • If the compilation phase succeeds they are linked together along with the AVR standard libraries (eg. giving you strcpy etc.)

A side-effect of all this is that you can consider the main sketch (the .ino files) to be C++ to all intents and purposes. The function prototype generation however can lead to obscure error messages if you are not careful.


Avoiding the pre-processor quirks

The simplest way of avoiding these idiosyncrasies is to leave your main sketch blank (and not use any other .ino files). Then make another tab (a .cpp file) and put your stuff into it like this:

#include <Arduino.h>

// put your sketch here ...

void setup ()
  {

  }  // end of setup

void loop ()
  {

  }  // end of loop

Note that you need to include Arduino.h. The IDE does that automatically for the main sketch, but for other compilation units, you have to do it. Otherwise it won't know about things like String, the hardware registers, etc.


Avoiding the setup/main paradigm

You don't have to run with the setup/loop concept. For example, your .cpp file can be:

#include <Arduino.h>

int main ()
  {
  init ();  // initialize timers
  Serial.begin (115200);
  Serial.println ("Hello, world");
  Serial.flush (); // let serial printing finish
  }  // end of main

Force library inclusion

If you run with the "empty sketch" concept you still need to include libraries used elsewhere in the project, for example in your main .ino file:

#include <Wire.h>
#include <SPI.h>
#include <EEPROM.h>

This is because the IDE only scans the main file for library usage. Effectively you can consider the main file as a "project" file which nominates which external libraries are in use.


Naming issues

  • Don't name your main sketch "main.cpp" - the IDE includes its own main.cpp so you will have a duplicate if you do that.

  • Don't name your .cpp file with the same name as your main .ino file. Since the .ino file effectively becomes a .cpp file this also would give you a name clash.


Declaring a C++-style class in the same, single .ino file (have heard of, but never seen working - is that even possible?);

Yes, this compiles OK:

class foo {
  public:
};

foo bar;

void setup () { }
void loop () { }

However you are probably best off to follow normal practice: Put your declarations in .h files and your definitions (implementations) in .cpp (or .c) files.

Why "probably"?

As my example shows you can put everything together in one file. For larger projects it is better to be more organized. Eventually you get to the stage in a medium to large-size project where you want to separate out things into "black boxes" - that is, a class that does one thing, does it well, is tested, and is self-contained (as far as possible).

If this class is then used in multiple other files in your project this is where the separate .h and .cpp files come into play.

  • The .h file declares the class - that is, it provides enough detail for other files to know what it does, what functions it has, and how they are called.

  • The .cpp file defines (implements) the class - that is, it actually provides the functions, and static class members, that make the class do its thing. Since you only want to implement it once, this is in a separate file.

  • The .h file is what gets included into other files. The .cpp file is compiled once by the IDE to implement the class functions.

Libraries

If you follow this paradigm, then you are ready to move the entire class (the .h and .cpp files) into a library very easily. Then it can be shared between multiple projects. All that is required is to make a folder (eg. myLibrary) and put the .h and .cpp files into it (eg. myLibrary.h and myLibrary.cpp) and then put this folder inside your libraries folder in the folder where your sketches are kept (the sketchbook folder).

Restart the IDE and it now knows about this library. This is really trivially simple, and now you can share this library over multiple projects. I do this a lot.


A bit more detail here.


My advice is to stick to the typical C++ way of doing things: separate interface and implementation into .h and .cpp files for each class.

There are a few catches:

  • you need at least one .ino file - I use a symlink to the .cpp file where I instantiate the classes.
  • you must provide the callbacks that the Arduino environment expects (setu, loop, etc.)
  • in some cases you will be surprised by the non-standard weird things that differentiate the Arduino IDE from a normal one, like automatic inclusion of certain libraries, but not others.

Or, you could ditch the Arduino IDE and try with Eclipse. As i mentioned, some of the things that are supposed to help beginners, tend to come in the way of more experienced developers.


I'm posting an answer just for completeness, after finding out and testing a way of declaring and implementing a class in the same .cpp file, without using a header. So, regarding the exact phrasing of my question "how many file types do I need to use classes", the present answer uses two files: one .ino with an include, setup and loop, and the .cpp containing the whole (rather minimalistic) class, representing the turning signals of a toy vehicle.

Blinker.ino

#include <TurnSignals.cpp>

TurnSignals turnSignals(2, 4, 8);

void setup() { }

void loop() {
  turnSignals.run();
}

TurnSignals.cpp

#include "Arduino.h"

class TurnSignals
{
    int 
        _left, 
        _right, 
        _buzzer;

    const int 
        amberPeriod = 300,

        beepInFrequency = 600,
        beepOutFrequency = 500,
        beepDuration = 20;    

    boolean
        lightsOn = false;

    public : TurnSignals(int leftPin, int rightPin, int buzzerPin)
    {
        _left = leftPin;
        _right = rightPin;
        _buzzer = buzzerPin;

        pinMode(_left, OUTPUT);
        pinMode(_right, OUTPUT);
        pinMode(_buzzer, OUTPUT);            
    }

    public : void run() 
    {        
        blinkAll();
    }

    void blinkAll() 
    {
        static long lastMillis = 0;
        long currentMillis = millis();
        long elapsed = currentMillis - lastMillis;
        if (elapsed > amberPeriod) {
            if (lightsOn)
                turnLightsOff();   
            else
                turnLightsOn();
            lastMillis = currentMillis;
        }
    }

    void turnLightsOn()
    {
        tone(_buzzer, beepInFrequency, beepDuration);
        digitalWrite(_left, HIGH);
        digitalWrite(_right, HIGH);
        lightsOn = true;
    }

    void turnLightsOff()
    {
        tone(_buzzer, beepOutFrequency, beepDuration);
        digitalWrite(_left, LOW);
        digitalWrite(_right, LOW);
        lightsOn = false;
    }
};