如何在 Arduino 程序中正确初始化 C++ 对象?

Posted

技术标签:

【中文标题】如何在 Arduino 程序中正确初始化 C++ 对象?【英文标题】:How can I properly initialize C++ objects within an Arduino program? 【发布时间】:2021-02-18 16:40:01 【问题描述】:

在我简单的 Arduino 项目中,为了保持整洁,我决定创建一个“模式管理器”来处理一种模式和另一种模式之间的传递。基本概念是每次我想改变模式,那么它会实例化下一个模式并替换上一个。

请注意,我在 OOP 和 Java / Scala 方面有 12 年以上的经验,但在 C++ 方面没有任何经验,只是让 Arduino 在没有太多结构的情况下完成其工作所需的东西。

经过一些研究,我设法创建了以下“界面”结构:

文件:modes/ModeManager.h。这应该保持对当前模式的引用,并将循环和实例化下一个模式委托给它(每个模式都会知道哪个是下一个)

#ifndef __MODE_MANAGER__
#define __MODE_MANAGER__

#include <stdint.h>
#include "modes/Mode.h"

class ModeManager 
 private:
  Mode *mode;

 public:
  ModeManager();
  ~ModeManager() ;
  virtual void loop(uint8_t voltage);
;

#endif

文件:modes/Mode.h 是运行程序真正循环功能的实际“模式”。它还处理实例化下一个模式

#ifndef __MODE__
#define __MODE__

#include <stdint.h>

class Mode 
 public:
  Mode() 
  virtual ~Mode() 
  virtual void loop(uint8_t voltage) = 0;
  virtual void getNext(Mode * target) = 0;
;

#endif

文件:modes/ModeManager.cpp

#include "ModeManager.h"

#include <stdint.h>
#include <Arduino.h>
#include "modes/Mode.h"
#include "modes/DummyMode.h"
#define VOLTAGE_NEXT_MODE 5

ModeManager::ModeManager() 
  DummyMode cmode = DummyMode();
  mode = & cmode; // I'm not sure how this should actually be done


void ModeManager::loop(uint8_t voltage) 
  if (voltage == VOLTAGE_NEXT_MODE) 
    Serial.println("Switching to next mode");
    mode->getNext(mode);
   else 
    Serial.println("Calling mode loop");
    mode->loop(voltage); // From here, nothing.
  

文件:modes/DummyMode.h

#ifndef __DUMMY_MODE__
#define __DUMMY_MODE__

#include "modes/Mode.h"

class DummyMode : public Mode 
 public:
  DummyMode();
  ~DummyMode() 
  virtual void loop(uint8_t voltage);
  virtual void getNext(Mode * target);
;

#endif

文件:modes/DummyMode.cpp

#include "modes/DummyMode.h"

#include <Arduino.h>

DummyMode::DummyMode() 
  Serial.println("Initialization of dummy mode");


void DummyMode::loop(uint8_t voltage) 
  Serial.print("Voltage: ");
  Serial.println(voltage);


void DummyMode::getNext(Mode * target) 
  DummyMode nextMode = DummyMode();
  target = &nextMode;

最后是我的main.cpp

#include <Arduino.h>

#include "modes/ModeManager.h"
#include "modules/memory.h"
#include "modules/monitor.h"

ModeManager * modeManager;

void setup() 
  pinMode(A0, INPUT);
  Serial.begin(9600);
  Serial.println("Init");
  ModeManager mm = ModeManager();
  modeManager = & mm;


void loop(void) 
  uint8_t voltage = map(analogRead(A0), 0, 1024, 0, 5);
  modeManager->loop(voltage);


现在,从理论上讲,我认为没有理由不这样做。在实践中,我 99.9% 确定我在初始化和指针方面做错了什么。 当我尝试运行此代码时,我得到以下序列号:

Init
Initialization of dummy mode
Calling mode loop

这意味着它在循环的第一次迭代中冻结,就在调用mode-&gt;loop(voltage);之前

谁能指出我正确的方向?再说一次,我真的没有 C++ 的经验,我关于如何制作这种结构的知识来自 C++ 编程的各种在线资源,包括这里的一些答案,所以请多多包涵

【问题讨论】:

DummyMode nextMode = DummyMode(); 在堆栈上创建一个局部变量,该变量在函数结束时被销毁,并且您将返回一个指向此堆栈空间的指针。 你对&amp;的所有使用都是错误的target = &amp;nextMode;modeManager = &amp; mm;你持有指向在作用域结束后不再存在的对象的指针。 如果您将 C++ 视为 C 和 Java 的混合体,那么您将很难编写 C++。它不是那些东西,并且足够复杂,值得阅读book。我不打算尝试回答,因为这段代码中存在太多问题,它会变成代码审查,而且那是一个不同的站点。 【参考方案1】:

这里:

DummyMode cmode = DummyMode();

您正在创建一个具有自动生命周期的DummyMode 实例cmode(通常称为堆栈上)。

然后您获取地址并将其分配给另一个变量:

mode = & cmode; // I'm not sure how this should actually be done

由于cmode 是一个具有自动生命周期的对象,它会在程序离开创建它的范围时被销毁。结果,存储在mode 中的指针将悬空并指向不再存在的对象。取消引用它会触发未定义的行为。

看起来您的意图是创建具有动态生命周期的对象。 您可以通过以下方式做到这一点:

mode = new DummyMode();

尽管如此,您有责任确保在不再需要对象时将其销毁。 这将通过delete 完成:

delete mode;

在更惯用/现代的 C++ 中,通常使用智能指针来管理此类对象的生命周期,而不是手动调用 delete。我看到 C++ 的标准库不适用于 Arduino,但似乎确实有一个智能指针可用:https://www.arduino.cc/reference/en/libraries/arxsmartptr/ 我建议好好看看使用它来更轻松地避免内存泄漏。

【讨论】:

使用new 关键字就像一个魅力。我将getNext 函数转换为Mode * getNext(); 以返回新指针并允许自己销毁前一个实例。有机会我会看看智能指针,但由于这是一个小项目,没有真正的优化需求,这是当时最快的解决方案!谢谢! 智能指针不是对速度的优化,而是对易用性和正确性的优化。

以上是关于如何在 Arduino 程序中正确初始化 C++ 对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何自己编写Arduino支持的C++类库

如何初始化静态 arduino (C) 数据结构,然后使用它们?

Arduino入门教程 第一章|C语言入门

bin固件转成arduino的程序

如何将 Android 应用程序正确连接到支持蓝牙的 Arduino 微控制器上的 RFCOMM 插座?

如何正确地通过蓝牙从 Android 向 Arduino 连续发送消息