Полиморфные классы не ведут себя, как ожидалось

Я программирую некоторый код Arduino, но все не совсем запланировано.
Что я здесь не так делаю? Я прочитал и попытался узнать о виртуальных функциях, но, возможно, я что-то упустил. Перейдите в раздел ВОПРОСЫ, чтобы узнать, какие именно вопросы мне нужны, но сначала объяснение:

Классы RGBPixel и colorGenerator являются производными от colorSource, который предоставляет открытые функции getR(), getG() и getB(), так что другой пиксель или модификатор цвета могут получить копию своего текущего цвета.
Классы, производные от colorGenerator, реализуют код генерации цвета, чтобы они могли генерировать свой собственный цвет, в то время как RGBPixels имеют родительский элемент colorSource *, поэтому они могут получить значение цвета из colorGenerator или другого RGBPixel.
В моем примере у меня есть один подкласс colorGenerator (CG_EmeraldWaters, который должен создавать мне различные оттенки зеленого и синего), а затем несколько RGBPixels в массиве. RGBPixels[0] должен принимать свое значение из экземпляра GC_EmeraldWaters, в то время как RGBPixels[1] берет свое значение из RGBPixels[0], [2] из [1], [n] из [n-1]. Пиксели, кажется, отлично вытягивают цвет из родительского элемента, но либо первый пиксель в цепочке неправильно запрашивает colorGenerator, либо colorGenerator не обновляется должным образом.

Чтобы обновить colorGenerator, класс colorController контролирует весь процесс:

colorController.h:

#ifndef _COLORCONTROLLER_H
#define _COLORCONTROLLER_H

#include <list>
#include "colorGenerator.h"
#include "RGBPixel.h"
#include "globals.h"
#include "Arduino.h"

unsigned long millis();

typedef std::list<colorGenerator> generatorList;

class colorController
{
    public:
    virtual bool refresh();
    protected:
    generatorList generators;
};

#endif //_COLORCONTROLLER_H

Как видите, у контроллера есть список colorGenerators и метод для их обновления (вызывается из loop()), который, если не переопределен в дочернем классе, делает это:

bool colorController::refresh()
{
    for (generatorList::iterator it = generators.begin(); it != generators.end(); ++it)
    it->refresh();
    bool dirty = false;
    for (int i = NUM_OF_LEDS-1; i >= 0; --i)
    dirty |= RGBPixels[i].refresh();
    return dirty;
}

Класс CC_Cascade (производный от colorController) устанавливает такие вещи:

CC_Cascade.h

#ifndef _CC_CASCADE_H
#define _CC_CASCADE_H

#include "colorController.h"

class CC_Cascade : public colorController
{
    public:
        CC_Cascade();
        ~CC_Cascade();
};

#endif //_CC_CASCADE_H

CC_Cascade.cpp

#include "CC_Cascade.h"
#include "CG_EmeraldWaters.h"

CC_Cascade::CC_Cascade()
{
    colorGenerator * freshBubblingSpring = new CG_EmeraldWaters();
    generators.push_back(*freshBubblingSpring);
    RGBPixels[0].setParent(freshBubblingSpring);
    RGBPixels[0].setDelay(40);
    for (int i = 1; i < NUM_OF_LEDS; ++i)
    {
    RGBPixels[i].setParent(&RGBPixels[i-1]);
    RGBPixels[i].setDelay(500-(9*i)); //FIXME: magic number only works for 50ish pixels
    }
}

CC_Cascade::~CC_Cascade()
{
    //TODO: delete generators
}

Пока все ясно? Позвольте мне обратить ваше внимание на функцию colorController:: refresh (). Что должно произойти, так это то, что каждый раз, когда он вызывается, в списке генераторов есть один colorGenerator (потому что конструктор CC_Cascade поместил его туда), который является CG_EmeraldWaters. Когда функция refresh () вызывается для этого (через итератор), он вызывает colorGenerator::refresh(), который, в свою очередь, вызывает updateColor(). В случае CG_EmeraldWaters это переопределяется, поэтому СЛЕДУЕТ вызывать CG_EmeraldWaters:: updateColor, давая бирюзовый цвет. Используя некоторые последовательные операторы записи для отладки, я вижу, что вызывается IN FACT colorGenerator::updateColor(), поэтому в этом случае я бы ожидал оранжевого цвета, НО ни один из них не влияет на цвет пикселей, которые все оставаясь фиолетовым цветом, как установлено в конструкторе CG_EmeraldWaters.
Немного позабавившись, я добавил следующую строку в colorGenerator::updateColor(): RGBPixels[0].setColor(255,127,0); Вместо оранжевого цвета, на который я надеялся, первый пиксель быстро переключался между фиолетовым и оранжевым, предполагая (ИМХО), что моя новая строка кода выполняла свою работу, но затем пиксель снова вытягивал свой первоначальный фиолетовый цвет из colorGenerator, и что каким-то образом colorGenerator:: updateColor() не меняет цвет colorGenerator (учитывая, что я не получаю ошибку компиляции, что это меняет?).

Итак, мои вопросы: (ВОПРОС)
1) Как я могу изменить значение colorSource::currentR(/G/B) из colorGenerator:: updateColor(), учитывая, что currentR (/ G / B) объявлен как защищенный в colorSource и что colorGenerator напрямую получен из colorSource?
2) При наличии экземпляра CG_EmeraldWaters, как я могу вызвать CG_EmeraldWaters::updateColor() через colorGenerator::refresh(), который наследует CG_EmeraldWaters, учитывая, что updateColor() объявлен как виртуальный в colorGenerator и переопределен в CG_EmeraldWaters?

Ниже приведен код colorGenerator и CG_EmeraldWaters:

colorSource.h:

#ifndef _COLORSOURCE_H
#define _COLORSOURCE_H

#include "Arduino.h"
#ifdef DEBUG
#include "colorGenerator.h" //FIXME: delete Me
#endif

//#define byte unsigned char
typedef byte colorStorage_t;

class colorSource
{
    public:
    colorSource();
        colorSource(colorStorage_t initialR, colorStorage_t initialG, colorStorage_t initialB);

    void setColor(colorStorage_t newR, colorStorage_t newG, colorStorage_t newB);
    //TODO: better implementation than this
    colorStorage_t getR();
    colorStorage_t getG();
    colorStorage_t getB();

    bool hasChanged();

    protected:
    colorStorage_t currentR;
    colorStorage_t currentG;
    colorStorage_t currentB;

    bool dirty;
#ifdef DEBUG
    friend colorGenerator; //FIXME: delete Me
#endif
};

#endif //_COLORSOURCE_H

colorSource.cpp:

#include "colorSource.h"

colorSource::colorSource()
{
    //nothing here
}

colorSource::colorSource(colorStorage_t initialR, colorStorage_t initialG, colorStorage_t initialB)
    :
    currentR(initialR),
    currentG(initialG),
    currentB(initialB)
{
    //intialised in the list
    Serial.println("Constructed Color Source with initial color");
}

void colorSource::setColor(colorStorage_t newR, colorStorage_t newG, colorStorage_t newB)
{
    currentR = newR;
    currentG = newG;
    currentB = newB;
}

colorStorage_t colorSource::getR()
{
    return currentR;
}

colorStorage_t colorSource::getG()
{
    return currentG;
}

colorStorage_t colorSource::getB()
{
    return currentB;
}

bool colorSource::hasChanged()
{
    return !dirty;
}

colorGenerator.h:

#ifndef _COLORGENERATOR_H
#define _COLORGENERATOR_H

#include "colorSource.h"
#ifdef DEBUG
#include "RGBPixel.h" //delete me, used for debugging!
#include "globals.h" //and me!
#endif

extern "C" unsigned long millis();

class colorGenerator : public colorSource
{
    public:
        colorGenerator(colorStorage_t initialR, colorStorage_t initialG, colorStorage_t initialB);
    bool refresh();

    protected:
    virtual void updateColor();

    unsigned long nextColorUpdate = 0;
    unsigned short delay = 40;
};

#endif //_COLORGENERATOR_H

colorGenerator.cpp:

#include "Arduino.h"

#include "colorGenerator.h"

colorGenerator::colorGenerator(colorStorage_t initialR, colorStorage_t initialG, colorStorage_t initialB)
    :
    colorSource(initialR,initialG,initialB)
{
    //intialised in the list
    //Serial.println("Constructed Color Generator");
}

bool colorGenerator::refresh()
{
#ifdef DEBUG
    Serial.print("colorGenerator::refresh()");
#endif
    if (millis() < nextColorUpdate)
    return false;
    nextColorUpdate = millis() + (unsigned long) delay;
    this->updateColor();
    return true;
}

void colorGenerator::updateColor() //this function gets called (even if it has been overridden in a child class), but the code in it doesn't have the desired effect
{
#ifdef DEBUG
    //Serial.print("colorGenerator::updateColor()");
    //RGBPixels[0].setColor(255,127,0);
#endif
    currentR = random(127,255);
    currentG = random(0,127);
    currentB = 0;
}

CG_EmeraldWaters.h:

#ifndef _CG_EMERALDWATERS_H
#define _CG_EMERALDWATERS_H

#include "colorGenerator.h"
#include "globals.h"
#include "RGBPixel.h"

class CG_EmeraldWaters : public colorGenerator
{
    public:
        CG_EmeraldWaters();

    protected:
        void updateColor();
};

#endif //_CG_EMERALDWATERS_H

CG_EmeraldWaters.cpp:

#include "Arduino.h"

#include "CG_EmeraldWaters.h"

CG_EmeraldWaters::CG_EmeraldWaters()
    :
    colorGenerator(255,0,255) //this color seems to stick! Changes made by updateColor() aren't propogated to the pixels.
{
    //initialised in list
    //Serial.println("Constructed Emerald Waters");
}

long random(long,long);

void CG_EmeraldWaters::updateColor() //this never seems to be called!
{
    currentR = 0;
    currentG = random(0,255);
    currentB = random(0,255);
}

И, наконец, основной файл эскиза:

#include "FastSPI_LED2.h"
#include <StandardCplusplus.h>

#include "colorController.h"
#include "RGBPixel.h"
#include "globals.h"
#include "CC_Cascade.h"

colorController * currentColorController;
RGBPixel RGBPixels[NUM_OF_LEDS];
struct CRGB ledString[NUM_OF_LEDS];

void setup()
{
#ifdef DEBUG
    //debugging:
    Serial.begin(9600);
    Serial.println("In Setup");
#endif

  // sanity check delay - allows reprogramming if accidently blowing power w/leds
    //delay(2000);
    LEDS.setBrightness(8);
    LEDS.addLeds<WS2801>(ledString, NUM_OF_LEDS);

    currentColorController = new CC_Cascade();
}

void writeValuesToString()
{
    for (int i = 0; i < NUM_OF_LEDS; ++i)
    ledString[i] = CRGB(RGBPixels[i].getR(),RGBPixels[i].getG(),RGBPixels[i].getB());
    LEDS.show();
}

void loop()
{
    static bool dirty = false; //indicates whether pixel values have changed since last hardware write
    //unsigned long lastHardwareWrite = 0; //time of last hardware write - only do this once per milisecond to avoid flicker (this method doesn't work, still flickers)

    dirty |= currentColorController->refresh();
    if (dirty)
    {
    dirty = false;
    writeValuesToString();
        delay(1); //to prevent flicker
    }
}

2 ответа

Решение

Ваша проблема связана с так называемой нарезкой объектов. Вот что происходит: когда вы объявляете список типов generatorList

typedef std::list<colorGenerator> generatorList;

его члены ограничены тем, что находится в colorGenerator, Ничто из производного класса не имеет значения, поэтому, когда вы нажимаете

colorGenerator * freshBubblingSpring = new CG_EmeraldWaters();
generators.push_back(*freshBubblingSpring);

CG_EmeraldWaters часть, которая также не находится в colorGenerator "отрезается"; вы в конечном итоге с версией colorGenerator,

Причина этого описана в статье в Википедии, ссылки на которую приведены выше. Чтобы решить эту проблему, измените список, чтобы он содержал указатели, предпочтительно умные указатели, указывающие на colorGenerator экземпляров. Тогда проблема нарезки больше не будет актуальна:

typedef std::list<unique_ptr<colorGenerator> > generatorList;
...
unique_ptr<colorGenerator> freshBubblingSpring(new CG_EmeraldWaters());
generators.push_back(freshBubblingSpring);
  1. Вы должны иметь возможность вызывать закрытые и защищенные методы базового класса из производного класса, если я что-то не упустил.

  2. Чтобы вызвать метод переопределения (например, virtual foo() определен в классе Base и переопределен в классе Derived), вы можете получить доступ к методу Base, вызвав derivedObj.Base::foo() в вашем коде.

Другие вопросы по тегам