Должен ли std::vectors широко использоваться для встроенных систем?

При написании кода на С++ для встроенной системы с ограниченными ресурсами ЦП и памяти общее правило заключается в создании объектов в стеке и избегании использования кучи, если это действительно необходимо. Конечно, это имеет много известных преимуществ, но с появлением STL и рекомендаций, рекомендующих std::vectors как эффективную структуру данных, нарушает ли это правило, о котором я упоминал, поскольку вектор будет использовать кучу?

Пример. В старые времена можно было объявить статические массивы с известными размерами, которые будут удовлетворять использованию. В настоящее время можно просто использовать векторы.

Мне не очень нравится этот переход, так как всегда существует вероятность того, что вектор не сможет выделить нужную память (напоминание: это для встроенных систем с ограниченной памятью). Использование массивов с известными размерами в стеке гарантирует, что во время компиляции будет свободное место.

Вызов метода резервирования(), но это делается во время выполнения.

Итак, это причина для беспокойства, или я просто параноик? Определенно гораздо проще использовать эти векторы, но для встроенной среды это может быть не очень хорошая идея?

Примечание.. Это не касается динамических и фиксированных массивов, а больше о том, как данные распределяются в памяти, что очень важно для моей среды. Например, некоторые люди сделают это так: скажем, что массив может вырасти или сжать от 1 до 10 элементов. Некоторые люди создавали бы массив, который покрывает этот размер в стеке, а NULL заканчивается в зависимости от текущего размера. Таким образом, избегается фрагментация, и мы гарантируем выделение во время компиляции. Однако переход на вектор сделал его намного более чистым, но за счет использования кучи и, возможно, имел дело с исключениями, если распределение не выполняется. Это то, о чем я беспокоюсь.

Ответ 1

Я считаю, что вы забыли одно очень важное свойство контейнеров STL: распределители.

Контейнер STL (независимо от того, a vector или иначе) получает всю свою память из своего распределителя (кроме основного элемента, который вы можете проверить с помощью sizeof). Поэтому в встроенной разработке он идеально подходит для предоставления выделенного распределителя, который:

  • будет выделяться из предварительно зарезервированной области памяти
  • свяжет максимальное потребление ресурсов для предотвращения OOM
  • ...

С появлением С++ 11 вы можете даже использовать контроллеры с установленными состояниями, так что один тип распределителя может указывать на разные пулы памяти.

Следовательно, использование std::vector или даже std::set или std::map не является несовместимым с стратегией предварительного распределения; помните, однако, что кроме std::vector в других контейнерах STL обычно есть некоторые служебные данные для каждого элемента, которые необходимо учитывать при определении размера области памяти, на которую они должны нарисоваться.

Ответ 2

"Зависит" может быть преуменьшением здесь. Используя резерв с векторами, вы можете эффективно распределять пространство для данных и предотвращать ненужные копии. Сама структура векторных данных будет сводиться к массиву, выделенному кучей, с размером, если компилятор достаточно хорош.

Кроме того, заметьте, я сказал кучу. Если вы предпочитаете выделять данные в стек, вы застреваете с массивами с фиксированным размером (включая std:: array).

И это также сильно зависит от компилятора, как вектор объединяется. Старые компиляторы могут (с большим упором на май) быть менее эффективными. Вот почему для встроенных систем вам действительно нужно знать архитектуру и компилятор, прежде чем делать широкие обобщения о том, что "хорошо" и что "плохо" использовать.

Ответ 3

std::vector следует использовать для динамически измененных массивов. Если вы знаете размер во время компиляции, вы можете использовать std::array.

Если вы знаете только приблизительный размер массива, с С++ 14, вы можете использовать std::dynarray. Этот массив имеет фиксированный размер, который не может быть изменен во время жизни объекта. Когда используется std::dynarray без распределителя, возможны дополнительные оптимизации, operator new может не вызываться, и будет использоваться распределение на основе стека.

Ответ 4

Использование массивов с известными размерами в стеке гарантирует, что во время компиляции будет свободное место.

Неверное предположение.

Когда вы получили большой вызов (даже не рекурсивный, а стек с большим уровнем), вы не можете быть уверены, что для вашего стека достаточно места для хранения объекта, который должен быть создан.

Существуют основные проверки (если размер вашего объекта превышает, по крайней мере, size_t), но я даже не думаю, что они стали обязательными по стандарту.

A - несколько чрезмерный - пример здесь:

#include <iostream>

template <unsigned long size>
struct BigObject
{
    unsigned long array[size * size];
    BigObject<size - 1> object;
};


template <>
struct BigObject<0>
{
    unsigned long array[1]; 
};

BigObject<900>& recurse(BigObject<900>& object1, unsigned long n)
{
    if (n == 0)
    {
        return object1;
    }

    BigObject<900> object;

    return recurse(object, n);
}

int main(int argc, char const *argv[])
{
    BigObject<900> object;
    recurse(object, 20);

    std::cout << sizeof(object) << std::endl;
    return 0;
}

http://ideone.com/pkHo43

Это сбой. И это не такой особый случай. У меня была проблема в 32-разрядном настольном приложении (так что ни одно из ограничений памяти) не выделяло слишком большой массив в стеке.

Ответ 5

A std::vector - это просто указатель и два size_t при компиляции с достаточными флагами оптимизации. Теперь память для size_t является пустой тратой, когда вы заранее знаете размер вектора и никогда не будете меняться.

Я бы сказал:

  • Фиксированный (малый) размер = > простой массив или std::array
  • Динамический размер или огромное количество элементов = > std::vector

И как упоминалось в комментариях, вы можете использовать большинство функций, например. <algorithms> в массивах с фиксированным размером, используя указатели для итераторов begin/end.

Например:

int array[8] = { 1,2,3,4,5,6,7,8 };
std::random_shuffle(array, array+8);