0

I am mostly working alone on my projects and I am not always sure in what 'others' consider to be good or pad practices.

Image you have these source files and headers

foo.cpp
foo.h
bar.cpp
foo.h

Lets say that I need functions in both foo and bar to print text on a 16x2 LCD using Arduino's LiquidCrystal.h library. I need to include the library and construct an object.

I can make 2 new files: lcd.cpp and lcd.h. And in lcd.cpp I can make an lcd object.

#include <LiquidCrystal.h>
#include "lcd.h"

LiquidCrystal lcd(A5, A4, A0, A2, A1, A3) ;

Both foo.cpp and bar.cpp should include lcd.h

For as far I know I can do these two things. I can either declare the lcd object with extern in lcd.h. Than every file which includes lcd.h has access to the global lcd object. So both in foo.cpp and bar.cpp I get to type

lcd.setCursor( 3, 1 ) ;

I believe this is very similar if not the same as Arduino does with the hardware Serial and Wire objects. You get to type

Serial.println(F("Hello world")) ;

In every source files which includes <Arduino.h>

Or I can make wrapper functions in lcd.cpp like:

void clear()
{
    lcd.clear();
}

void setCur(byte x, byte y)
{
    lcd.setCursor(x,y);
}

Using the wrapper functions does give me the infrastructure to implement easy outline and position functions like

void printAt(byte x, byte y, String text)
{
    lcd.setCursor(x,y);
    lcd.print(text);
} 

Are there different options for this scenario which are "better" for a reason? Is one of the two methods considered "better" than the other? Or does it not really matter and is this matter highly subjective?

bask185
  • 377
  • 1
  • 3
  • 12
  • It is not best practice to use C++ for 1990s design, very resource-constrained 8 bit MCUs. For example your static storage duration constructors like `LiquidCrystal` will lag down MCU boot-up - and the reason is that C++ is simply unsuitable for this target. Since you've already swallowed the C++ camel, I wouldn't worry about best practices from there on, just hack away. – Lundin Aug 13 '21 at 11:58

2 Answers2

2

This works and is threadsafe too

header file :

class lcd
{
};

static lcd& get_lcd()
{
    static lcd instance;
    return instance;
}

Related to this : https://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton

Then you need to build up the lcd class and add it's implementation to a cpp file

Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
2

We have solved similar problems in two different ways.

  1. Singleton Where it is only possible for an object have a single instance I would go the route Pepijn Kramer suggests, create a singleton object. The link mentioned in his post uses the design we also use where the static object is held in the object. This makes it more C++ instead of separate floating functions C style.
class lcd
{
  public:
    lcd& instance() {
      static lcd;
      return lcd
    }

    void print();

  private:
    // Hide the constructors and destructor such that you can only 
    // use the instance function to create the object.
    lcd(); 
    ~lcd();
};

You only have to include lcd.h in your foo.cpp and bar.cpp source files not in the header to access the lcd like:

lcd::instance().print();

There is a problem here, you can access the singleton at any time any where, including during construction of other objects, prior to the point where lcd is properly initialized. You know when you set it's pins and settings. This could lead to unexpected behavior.

  1. with_XXX In the second solution we pass a pointer to bar and foo after lcd has been properly initialized. It is now nolonger a required for lcd to be a singleton object but it could.
// No need to include the header of lcd you can forward declare it 
// to prevent circular includes. Just include it in the .cpp files
class lcd 

class foo {
 public:
   void with_lcd(lcd& the_lcd_object) {
     the_lcd = &the_lcd_object;
   }

 private:
   lcd* the_lcd;
};

main() {
  // Initialize lcd
  my_lcd.initialize()

  my_foo.with_lcd(my_lcd);
  my_bar.with_lcd(my_lcd);

  // And your good to go.
}

You can now check in your code of foo and bar if you have lcd. This way you prevent circulair includes (for larger projects a problem) and you are sure it have been initialized once you have the pointer.

This method you can also use when you have multiple objects of the same type and you need to assign them when you have more information. For instance after node initialization and you know which node you are.

Bart
  • 1,405
  • 6
  • 32