【问题标题】:Polymorphic classes not behaving as expected多态类的行为不符合预期
【发布时间】:2013-12-09 08:43:52
【问题描述】:

我正在编写一些 arduino 代码,但事情并没有完全按计划进行。
我在这里做错了什么?我已经阅读并尝试对自己进行有关虚拟功能的教育,但也许我错过了一些东西。对于我需要回答的实际问题,请转到 QUESTIONSHERE,但首先需要一些解释:

RGBPixel 和 colorGenerator 类都派生自 colorSource,它提供公共函数 getR()、getG() 和 getB(),以便另一个像素或颜色修改器可以获取其当前颜色的副本。
从 colorGenerator 派生的类实现颜色生成代码,以便它们可以生成自己的颜色,而 RGBPixel 具有 colorSource *parent 成员,因此它们可以从 colorGenerator 或另一个 RGBPixel 获取颜色值。
在我的示例中,我有一个 colorGenerator 子类(CG_EmeraldWaters,它应该为我创建各种绿色和蓝色),然后是一个数组中的多个 RGBPixel。 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 的颜色(假设我没有收到编译错误,它在改变什么?)。

所以我的问题是:(QUESTIONSHERE)
1) 我如何从 colorGenerator::updateColor() 中更改 colorSource::currentR(/G/B) 的值,因为 currentR(/G/B) 在 colorSource 中被声明为 protected 并且 colorGenerator 直接派生自颜色来源?
2) 给定一个 CG_EmeraldWaters 的实例,我如何通过 colorGenerator::refresh() 调用 CG_EmeraldWaters::updateColor(),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
    }
}

【问题讨论】:

  • 1.您应该能够从其子类调用超类的私有和受保护方法,除非我遗漏了什么。
  • 这个问题太大了 - 在第一个代码 sn-p 之后我很想停止阅读,但幸运的是我在顶部发现了问题。一般来说,您应该尝试构建一个小的(最好是微小的)独立示例来显示问题。

标签: c++ inheritance polymorphism arduino


【解决方案1】:

您的问题是由于所谓的object slicing。这是发生了什么:当你声明一个generatorList类型的列表时

typedef std::list<colorGenerator> generatorList;

其成员仅限于colorGenerator 中的内容。派生类中的任何内容都无关紧要,所以当你推送时

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

不在colorGenerator 中的CG_EmeraldWaters 部分被“切掉”;你最终会得到一个colorGenerator 的版本。

上面链接的维基百科文章中描述了这样做的原因。要解决此问题,请将列表更改为包含指针,最好是smart pointers,指向colorGenerator 实例。那么切片问题将不再相关:

typedef std::list<unique_ptr<colorGenerator> > generatorList;
...
unique_ptr<colorGenerator> freshBubblingSpring(new CG_EmeraldWaters());
generators.push_back(freshBubblingSpring);

【讨论】:

  • 谢谢你,我正想问我怎么可能实现它,然后你编辑了一个例子!只是谷歌搜索 unique_ptr - 我想知道我将如何尊重迭代器。我需要**双重尊重吗?现在看看我是否有可用的 unique_ptr...
  • @M_M std::unique_ptr overrides the -&gt; and * operators 提供对底层对象的访问。因此,您可以使用两个星号或星号和“箭头”-&gt; 来双重取消引用。
  • 我没有可用的 unique_ptr(我使用的 STL 标头不是 C++11),但我对普通指针相当满意,特别是因为我有点空格-受限于arduino板上。我遇到了**it.refresh();*it-&gt;refresh();it-&gt;*refresh() 的编译错误,所以我使用了{colorGenerator * thisGenerator = *it;thisGenerator-&gt;refresh();}。直到明天我才能访问目标硬件,看看它是否有效,但我会告诉你的!与此同时,谁能想到一个语法正确的单行字?
  • 试试(*it)-&gt;refresh吧?
  • @M_M 请注意,如果您要使用常规指针,那么您将在使用new 分配的对象上调用delete。智能指针可让您避免这种繁琐的工作。
【解决方案2】:
  1. 您应该能够从派生类调用基类的私有和受保护方法,除非我遗漏了什么。

  2. 要调用被覆盖的方法(例如,虚拟 foo() 在 Base 类中定义并在 Derived 类中被覆盖),您可以通过在代码中调用 derivedObj.Base::foo() 来访问 Base 方法。

    李>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-27
    • 2022-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多