Search code examples
c++arduino-unoplatformio

Initialize outer scope variable in class function


I am new to C++ I come from a C# background. I am creating an Arduino project using c++ using vs code thanks to https://docs.platformio.org/ .

The following code works and turns on my led strip:

main.cpp

#include <Arduino.h>   
#include <LedStrip.h>

// pin where led strip is connected
#define LED_STIP_PIN 4

// number of leds in the strip
#define LED_STRIP_COUNT 7

// My wrapper class
LedStrip myLedStripWrapper;


void setup() {
   // empty
}

void loop() {
   
    myLedStripWrapper.setup(LED_STRIP_COUNT, LED_STIP_PIN);

    // myLedStripWrapper.foo1();  // <<<HERE-1>>>
    
    // wait forever
    while (true) {
        delay(1);
    }       
}

LedStrip.h

#include <Adafruit_NeoPixel.h>

class LedStrip
{

public:
    
    Adafruit_NeoPixel strip;

    void setup(uint16_t led_count, uint16_t pin)
    {
        // Declare our NeoPixel strip object:
        Adafruit_NeoPixel a(led_count, pin, NEO_GRB + NEO_KHZ800);                
        strip = a;

        // INITIALIZE NeoPixel strip
        strip.begin();            
        strip.show();            
        strip.setBrightness(15); 

        foo1();     // <<<HERE-2>>> <------------------- calling foo1 from here turns on the led strip           
    }

    
    // Color led strip blue
    void foo1()
    {
        uint32_t color = strip.Color(0, 0, 100);
        for (uint16_t i = 0; i < strip.numPixels(); i++)
        {                                  // For each pixel in strip...
            strip.setPixelColor(i, color); //  Set pixel's color (in RAM)
            strip.show();                  //  Update strip to match
            // delay(100);
        }
    }
};

Note that in the code I have labels <<<HERE-1>>> and <<<HERE-2>>>. I want to call function foo1 from <<<HERE-1>>>. Why is it that if I uncomment <<<HERE-1>>> and comment <<<HERE-2>>> the code does not work and the LED strip does not turn on? In other words I do not want to call foo1 from within the class wrapper. I am just calling it form there because it only works if I call it form there.


Solution

  • The writers of that class made a grade-school C++ mistake and didn't correctly observe the Rule of Three. This means an Adafruit_NeoPixel object can be copied when it is not safe to do so.

    void setup(uint16_t led_count, uint16_t pin)
    {
        // Declare our NeoPixel strip object:
        Adafruit_NeoPixel a(led_count, pin, NEO_GRB + NEO_KHZ800);                
        strip = a; // copy here. Both a and strip point to a shared resource
    
        // INITIALIZE NeoPixel strip
        strip.begin();            
        strip.show();            
        strip.setBrightness(15); 
    } // a goes out of scope here and frees the shared resource in its 
      // destructor. Oops.
    

    The easiest fix is to initialize strip in LedStrip's constructor where it doesn't need to be copied.

    To ensure strip isn't copied, you're going to have to prevent LedStrip from being copied or implement special member functions that allow LedStrip to be copied without copying strip. In the example below I will simply prevent copying.

    If copying is required consider replacing Adafruit_NeoPixel strip; with std::shared_ptr<Adafruit_NeoPixel> strip; so a pointer will be copied rather than an object that can turn into a bomb when copied.

    class LedStrip
    {
    
    public:
        
        Adafruit_NeoPixel strip; // you sure you want this public?
        //  adding constructor so we don't have to copy a Adafruit_NeoPixel  object
        LedStrip(uint16_t led_count, uint16_t pin): 
            strip(led_count, pin, NEO_GRB + NEO_KHZ800) // this is a member initializer list
                                                        // It allows us to construct strip 
                                                        // without having to copy anything.
                                                        // more on that later
        {
            // INITIALIZE NeoPixel strip
            strip.begin();            
            strip.show();            
            strip.setBrightness(15); 
        }
    
        // preventing copying of LedStrip
        LedStrip(const LedStrip& ) = delete;
        LedStrip & operator=(const LedStrip& ) = delete;
        // note if the compiler doesn't like the = delete, remove it, make 
        // the copy constructor and assignment operator private, and do not 
        // implement them
    
        
        // Color led strip blue
        void foo1()
        {
            uint32_t color = strip.Color(0, 0, 100);
            for (uint16_t i = 0; i < strip.numPixels(); i++)
            {                                  // For each pixel in strip...
                strip.setPixelColor(i, color); //  Set pixel's color (in RAM)
                strip.show();                  //  Update strip to match
                // delay(100);
            }
        }
    private:
    /* only needed if the = delete trick above doesn't work.
        LedStrip(const LedStrip& );
        LedStrip & operator=(const LedStrip& );
    */
    };
    

    Then

    LedStrip myLedStripWrapper;
    

    becomes

    LedStrip myLedStripWrapper(LED_STRIP_COUNT, LED_STIP_PIN);
    

    and

    myLedStripWrapper.setup(LED_STRIP_COUNT, LED_STIP_PIN);
    

    vanishes from the world, never to be seen again.

    Documentation on the Member Initializer List.

    Documentation on std::shared_ptr