Лучшие практики для полиморфизма во время выполнения для Embedded C ++? ROMable полиморфизм во время выполнения? - PullRequest
1 голос
/ 27 февраля 2020

Мне требуется полиморфизм времени исполнения в моем встроенном проекте C ++. В своем исследовании лучших практик для встроенного c ++ я обнаружил, что рекомендуется объявлять объекты в памяти stati c и максимально избегать динамического выделения c (new / delete / malloc / free), если не полностью .

Ниже приводится то, что я придумал для полиморфизма во время выполнения во встроенном C ++. Идея состоит в том, что вы динамически распределяете объект InterfaceA, немедленно используете его и затем удаляете. У вас нет экземпляров этого класса интерфейса. Любые постоянные данные будут получены (например, get_index ()) из класса интерфейса и сохранены извне. В любое время, когда вам нужно работать с интерфейсными функциями, вы перераспределяете интерфейс, немедленно его используете и удаляете. Это позволит избежать использования кучи в максимально возможной степени.

Это кажется не совсем оптимальным. Поскольку я не храню в интерфейсе какие-либо данные о состоянии c, я надеюсь, что у меня есть возможность использовать интерфейс статически.

Каков наилучший способ выполнения полиморфизма во время выполнения, избегая при этом динамическое c выделение?

Бонус: есть ли способ сделать полиморфизм во время выполнения, сохраняя приложение (в основном) доступным для чтения?

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;
}

Ответы [ 2 ]

1 голос
/ 28 февраля 2020

Как предлагается в другом ответе и комментариях, я бы не советовал использовать динамическое распределение памяти c во встроенных средах. Очень важно иметь детерминированное поведение в отношении вашей ограниченной памяти. При работе с голым металлом действительно трудно / невозможно отловить исключения из памяти. Использование RTOS даст вам немного больше гибкости, но не намного.

Однако это возможно. Чтобы сделать вашу программу полиморфизмом без динамического выделения памяти c, вы можете добиться этого, используя union для C ++ 11 или вариант для C ++ 17. Вы можете использовать их для статического выделения памяти и последующей инициализации реального объекта. Для поддержки нескольких объектов вы можете использовать массив ограниченного размера.

В следующем примере создается массив объединений stati c для вашего интерфейса. Заводская функция инициализирует память в объединении. Функция очистки вызывает деструктор для очистки объединения.

#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 :: варианта. Я подозреваю это будет проще, поскольку вам не нужно инициализировать и очищать память вручную.

Я надеюсь, что это ответ на ваш вопрос. Да, можно использовать полиморфизм и иметь распределение c.

Если вы ищете дополнительные примеры использования C ++ во встроенном устройстве, вы можете взглянуть на код MBED OS . Я нахожу это очень ясным кодом C ++ (мое мнение), и мне интересно, что ARM, один из крупнейших разработчиков mcu в мире, выбрал C ++ для своего кода.

0 голосов
/ 27 февраля 2020

Полиморфизм во время выполнения, RTTI и динамическое распределение c не должны использоваться в приложениях со встроенной системой без использования металла, точка. Основная причина в том, что это не имеет никакого смысла . Но также потому, что встроенные системы всегда должны вести себя детерминистически.

Вместо того, чтобы сосредоточиться на том, какие языковые функции вы хотите использовать, сфокусируйтесь на цели приложения - реальной проблеме, которую вы пытаетесь решить - и затем проектируйте программу соответственно. Использование языковых возможностей только ради этого и приводит к появлению узких мест в производительности, метапрограммированию, странным ошибкам и распространению кода.

Это на самом деле основная причина, по которой C больше подходит для встраиваемых систем: большинство функций, которые вы не должны использовать, просто недоступны. Вы можете писать голые программы на C ++, но это требует обширных навыков и дисциплины, чтобы знать, какое ограниченное подмножество C ++ можно безопасно использовать. К сожалению, около 90% всех программистов C ++ не имеют того, что нужно для этого (даже если они сами так думают), поэтому лучше избегать C ++ полностью.

...