【问题标题】:What is good practice when source files need to use the same object当源文件需要使用相同的对象时有什么好的做法
【发布时间】:2021-08-13 09:54:19
【问题描述】:

我大部分时间都是独自完成我的项目,我并不总是确定“其他人”认为哪些是好的做法或垫底做法。

图片你有这些源文件和头文件

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

假设我需要 foo 和 bar 中的函数来使用 Arduino 的 LiquidCrystal.h 库在 16x2 LCD 上打印文本。我需要包含库并构造一个对象。

我可以制作 2 个新文件: lcd.cpplcd.h。 在lcd.cpp 中,我可以制作一个 lcd 对象。

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

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

foo.cppbar.cpp 都应包含 lcd.h

据我所知,我可以做这两件事。 我可以在 lcd.h 中使用 extern 声明 lcd 对象。比每个包含 lcd.h 的文件都可以访问全局 lcd 对象。所以在 foo.cpp 和 bar.cpp 我都可以输入

lcd.setCursor( 3, 1 ) ;

我相信这与 Arduino 对硬件 Serial 和 Wire 对象的做法非常相似,如果不一样的话。你可以输入

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

在每个包含的源文件中

或者我可以在 lcd.cpp 中创建包装函数,例如:

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

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

使用包装函数确实为我提供了实现简单轮廓和定位函数的基础设施,例如

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

在这种情况下,是否有不同的选项“更好”? 这两种方法中的一种是否被认为比另一种“更好”?或者它真的不重要,这件事是高度主观的吗?

【问题讨论】:

  • 在 1990 年代的设计中使用 C++ 并不是最佳实践,8 位 MCU 资源非常有限。例如,您的静态存储持续时间构造函数(如LiquidCrystal)将延迟 MCU 启动 - 原因是 C++ 根本不适合此目标。既然你已经吞下了 C++ 骆驼,我就不用担心从那里开始的最佳实践了,干吧。

标签: c++ embedded 8-bit


【解决方案1】:

这很有效,而且也是线程安全的

头文件:

class lcd
{
};

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

与此相关: https://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton

然后你需要建立 lcd 类并将它的实现添加到一个 cpp 文件中

【讨论】:

    【解决方案2】:

    我们以两种不同的方式解决了类似的问题。

    1. 单例 如果一个对象只能有一个实例,我会按照 Pepijn Kramer 建议的路线创建一个单例对象。他的帖子中提到的链接使用了我们也使用的设计,其中静态对象保存在对象中。这使它更像 C++,而不是单独的浮动函数 C 风格。
    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();
    };
    

    您只需在您的foo.cppbar.cpp 源文件中包含lcd.h,而不是在标题中即可访问液晶显示器,例如:

    lcd::instance().print();
    

    这里有一个问题,你可以在任何时间任何地点访问单例,包括在其他对象的构造过程中,在 lcd 正确初始化之前。你知道什么时候设置它的引脚和设置。这可能会导致意外行为。

    1. with_XXX 在第二个解决方案中,我们在 lcd 正确初始化后传递一个指向 bar 和 foo 的指针。现在不再需要 lcd 成为单例对象,但它可以。
    // 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.
    }
    

    如果您有液晶显示器,您现在可以签入您的 foo 和 bar 代码。这样可以防止循环包含(对于较大的项目会出现问题),并且一旦获得指针就可以确定它已被初始化。

    当您有多个相同类型的对象并且当您有更多信息时需要分配它们时,您也可以使用此方法。例如在节点初始化之后,你就知道你是哪个节点了。

    【讨论】:

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