Global object constructors

It can be convenient to use C++ objects as global variables. You must be careful about what you do in the constructor, however.

The first code example is the bad example, don't do this.

#include "Particle.h"

SerialLogHandler logHandler;

class MyClass {
public:
    MyClass();
    virtual ~MyClass();

    void subscriptionHandler(const char *eventName, const char *data);
};

MyClass::MyClass() {
    // This is generally a bad idea. You should avoid doing this from a constructor.
    Particle.subscribe("myEvent", &MyClass::subscriptionHandler, this);
}

MyClass::~MyClass() {

}

void MyClass::subscriptionHandler(const char *eventName, const char *data) {
    Log.info("eventName=%s data=%s", eventName, data);
}

// In this example, MyClass is a globally constructed object.
MyClass myClass;

void setup() {

}
void loop() {

}

Making MyClass myClass a global variable is fine, and is a useful technique. However, it contains a Particle.subscribe call in the constructor. This may fail, or crash the device. You should avoid in a global constructor:

  • All functions in the Particle class (Particle.subscribe, Particle.variable, etc.)
  • Creation of threads
  • Hardware initialization including I2C and SPI
  • Calls to delay()
  • Any class that depends on another globally initialized class instance

The reason is that the order that the compiler initializes global objects varies, and is not predictable. Thus sometimes it may work, but then later it may decide to reorder initialization and may fail.


One solution is to use two-phase setup. Instead of putting the setup code in the constructor, you put it in a setup() method of your class and call the setup() method from the actual setup(). This is the recommended method.

#include "Particle.h"

SerialLogHandler logHandler;

class MyClass {
public:
    MyClass();
    virtual ~MyClass();

    void setup();

    void subscriptionHandler(const char *eventName, const char *data);
};

MyClass::MyClass() {
}

MyClass::~MyClass() {
}

void MyClass::setup() {
    Particle.subscribe("myEvent", &MyClass::subscriptionHandler, this);
}

void MyClass::subscriptionHandler(const char *eventName, const char *data) {
    Log.info("eventName=%s data=%s", eventName, data);
}

// In this example, MyClass is a globally constructed object.
MyClass myClass;

void setup() {
    myClass.setup();
}

void loop() {

}

Another option is to allocate the class member using new instead.

#include "Particle.h"

SerialLogHandler logHandler;

class MyClass {
public:
    MyClass();
    virtual ~MyClass();

    void subscriptionHandler(const char *eventName, const char *data);
};

MyClass::MyClass() {
    // This is OK as long as MyClass is allocated with new from setup
    Particle.subscribe("myEvent", &MyClass::subscriptionHandler, this);
}

MyClass::~MyClass() {

}

void MyClass::subscriptionHandler(const char *eventName, const char *data) {
    Log.info("eventName=%s data=%s", eventName, data);
}

// In this example, MyClass is allocated in setup() using new, and is safe because
// the constructor is called during setup() time.
MyClass *myClass;

void setup() {
    myClass = new MyClass();
}

void loop() {

}