簡體   English   中英

嵌入式 C++ 運行時多態的最佳實踐? ROMable 運行時多態性?

[英]Best practices for run-time polymorphism for Embedded C++? ROMable run-time polymorphism?

我的嵌入式 C++ 項目需要運行時多態性。 在我對嵌入式 c++ 最佳實踐的研究中,我發現建議在靜態內存中聲明對象,並盡可能避免動態分配(new/delete/malloc/free),如果不是完全的話。

下面是我為嵌入式 C++ 中的運行時多態性提出的內容。 這個想法是你動態分配 InterfaceA 對象,立即使用它然后刪除它。 您不持有該接口類的任何實例。 任何持久數據都將從接口類中獲取(例如 get_index() )並存儲在外部。 任何時候需要對接口功能進行操作時,重新分配接口,立即使用並刪除它。 這將盡可能避免使用堆。

這似乎不是完全最優的。 由於我沒有在接口中存儲任何靜態數據,因此我希望有一種方法可以靜態使用該接口。

在避免動態分配的同時進行運行時多態性的最佳方法是什么?

獎勵:有什么方法可以在保持應用程序(主要是)ROMable 的同時進行運行時多態性?

class A
{
    public:
        int index;
        int init(int type);
}

int A::init(int type)
{
    Interface* interface = SelectInterface(type);
    index = interface->get_index(type);
    delete interface;
}

然后我有以下界面:

// ----------- INTERFACES -------------- //

class Interface
{
    virtual int get_index() = 0;
}

// This is the interface factory
Interface* SelectInterface(int type)
{
    if (type == 0)
    { 
        return new InterfaceA();
    }
    else if (type == 1)
    {
        return new InterfaceB();
    }

    return null;
}

class InterfaceA :: public Interface
{
    InterfaceA();
    int get_index();
} 

int InterfaceA::get_index()
{
    return 5;
}

class InterfaceB :: public Interface
{
    InterfaceB();
    int get_index();
} 

int InterfaceB::get_index()
{
    return 6;
}

正如其他答案和評論中所建議的,我不建議在嵌入式環境中使用動態內存分配。 對於有限的記憶,有確定性的行為是非常重要的。 當運行裸機時,真的很難/不可能捕捉到內存不足的異常。 使用 RTOS 會給你更多的靈活性,但不會太多。

然而這是可能的。 為了使您的程序在沒有動態內存分配的情況下使用多態性,您可以通過使用 C++11 的聯合或 C++17 的變體來實現這一點。 您可以使用它們來靜態分配內存並稍后初始化實際對象。 要支持多個對象,您可以使用有限大小的數組。

以下示例為您的接口構造一個靜態聯合數組。 工廠函數初始化聯合中的內存。 clear 函數調用析構函數來清除聯合。

#include <array>

template<int SIZE>
class Buffer
{
  private:
    union DataUnion 
    {
      // These are required. Please check the documenation of unions.
      DataUnion() {};
      ~DataUnion() {};

      // Actual data in the union.
      InterfaceA a;
      InterfaceB b;
    };

    std::array<DataUnion, SIZE> dataArray;

  public:
    Buffer() = default;
    ~Buffer() = default;

    // This is the interface factory
    Interface* SelectInterface(int type)
    {
      // This function will return null when there is no space. You can easily deal with this 
      // compared to an exception.
      Interface* pointer = nullptr;

      // First check there is space in the array
      // Pseudo code
      if(dataArray.has_space())
      {
        // Pseudo code to get a free union
        DataUnion& union = GetFreeUnion();

        if (type == 0)
        { 
          // Initialize the memory in the union to be of type A.
          // Use the placement new.
          new &(union.a) InterfaceA{};
          pointer = &(union.a);
        }
        else if (type == 1)
        {
          // Initialize the memory in the union to be of type B.
          // Use the placement new.
          new &(union.b) InterfaceB{};
          pointer = &(union.b);
        }
      }
      return pointer;
    }

    // After your done with the object you need to clear the memory.
    void Clear(Interface* pointer_to_data)
    {
      // Pseudo code to find the index in the array.
      int index = FindIndex(pointer_to_data)
      DataUnion& union = dataArray[index];

      // Pseudo code to retrieve the type stored at that index.
      // You need to keep track of that, which is not in this example.
      int type = GetType(index);

      // Now call the destructor of the object to clear the union.
      if(type = 0)
      {
        union.a.~InterfaceA();
      }
      else if(type = 1)
      {
        union.b.~InterfaceB();
      }

      // Update the administration that index is free.
    }
};

// Define the buffer.
Buffer<10> buffer;

main()
{
  // Initiate an instance of a.
  Interface* pA = buffer.SelectInterface(0);
  if(nullptr != pA)
  {
    // Do something.
  }

  // Initiate an instance of b.
  Interface* pB = buffer.SelectInterface(1);
  if(nullptr != pB)
  {
    // Do something.
  }

  // Stop using a.
  buffer.Clear(pA);

  // And on
}

您必須考慮以下限制:

  1. InterfaceA、InterfaceB 和其他接口的大小相當。 如果你有一個更大的派生類,它會破壞內存分配。
  2. 您應該對同時需要多少個對象有一個大致的了解。 即使您不使用緩沖區,也會始終分配緩沖區。
  3. 工會確實有局限性,需要特別照顧。 明智地使用它們! 閱讀文檔和這篇文章
  4. 您需要實現代碼來跟蹤哪些索引被占用。

在這一點上,我還沒有使用 std::variant 選項。 懷疑它會更簡單,因為您不需要手動初始化和清除內存。

我希望這回答了你的問題。 是的,可以使用多態並進行靜態分配。

如果您正在尋找有關如何在嵌入式設備中使用 C++ 的更多示例,您可以查看MBED OS的代碼。 我發現它非常清晰的 C++ 代碼(我的觀點)並且我覺得有趣的是,ARM,世界上最大的 mcu 設計者之一,選擇了 C++ 作為他們的代碼。

運行時多態性、RTTI 和動態分配不應在裸機嵌入式系統應用程序中使用。 主要原因是它沒有任何意義 但也因為嵌入式系統應該始終具有確定性的行為。

與其關注您想要使用的語言特性,不如關注應用程序的目的——您試圖解決的實際問題——然后相應地設計程序。 僅僅為了它而使用語言特性是你最終遇到性能瓶頸、元編程、奇怪的錯誤和一般代碼膨脹的方式。

這實際上是 C 更適合嵌入式系統的主要原因:您不應該使用的大多數功能根本不可用。 可以用 C++ 編寫裸機程序,但它需要廣泛的技能和紀律,才能知道可以安全使用的 C++ 的哪些受限子集。 不幸的是,大約 90% 的 C++ 程序員不具備這樣做的能力(即使他們自己也這么認為),因此最好完全避免使用 C++。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM