Когда вы будете использовать массив, а не вектор/строку?

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

Я заметил, что многие ответы на SO предлагают использование векторов над массивами и строки над массивами char. Кажется, что это "правильный" способ кодирования на С++.

Что все сказано, когда стоит использовать классический массив / char * (если когда-либо)?

Ответ 1

При написании кода, который должен использоваться в других проектах, в частности, если вы нацеливаете специальные платформы (встроенные, игровые консоли и т.д.), где STL может отсутствовать.

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

Ответ 2

Никогда.

Если исходный массив кажется лучшим решением, чем вектор (по другим причинам здесь), я использую std:: tr1:: array или std:: array в компиляторах С++ 11 ( или boost:: array). Это просто делает проверки, которые я бы сделал в любом случае, и использование значения размера делает DRY автоматически реализованным (например, я использую размер в циклах, чтобы будущие изменения объявления массива автоматически работали).

Реализация массива "есть" необработанный массив с проверками и постоянная размерность в любом случае, поэтому легко получить код массива во встроенном коде, потому что код не очень "слишком умный" для любого компилятора. Что касается шаблонов поддержки компилятора, я бы скопировал заголовки boost в моем коде, чтобы позволить мне использовать этот вместо необработанных массивов. Потому что явно слишком легко ошибаться при использовании необработанных массивов. Необработанные массивы являются злыми. Они подвержены ошибкам.

И это очень хорошо работает с алгоритмами STL (если доступно).

Теперь есть два случая, когда вы нуждаетесь, чтобы использовать необработанные массивы (обязательство): когда вы находитесь в коде C-кода (не обмениваясь данными с кодом C, но записывая только в C-only part кода, как библиотека C). Но тогда это другой язык.

Другая причина заключается в том, что компилятор вообще не поддерживает шаблоны...

Ответ 3

Этот вопрос можно разделить на две части:

  • Как мне управлять памятью для данных плоского массива?
  • Как мне получить доступ к элементам плоского массива?

Я лично предпочитаю использовать std::vector для управления памятью, за исключением случаев, когда мне нужно поддерживать совместимость с кодом, который не использует STL (т.е. когда взаимодействует с прямым кодом C). Намного сложнее сделать безопасный код с необработанными массивами, выделенными через new или malloc (отчасти потому, что очень легко забыть, что вам нужно беспокоиться об этом). См. Любую статью в RAII по причинам.

На практике std::vector реализуется как плоский массив. Таким образом, всегда можно вытащить необработанный массив и использовать шаблоны доступа в стиле C. Обычно я начинаю с синтаксиса оператора векторного индекса. Для некоторых компиляторов при создании отладочной версии векторы обеспечивают автоматическую проверку границы. Это медленное (часто 10-кратное замедление для жестких циклов), но полезно найти определенные типы ошибок.

Если профилирование на определенной платформе указывает на то, что оператор [] является узким местом, я переключаюсь на прямой доступ к необработанному массиву. Интересно, что в зависимости от компилятора и ОС иногда может быть быстрее использовать вектор STL, чем необработанный массив.

Вот некоторые результаты из простого тестового приложения. Он был скомпилирован с Visual Studio 2008 в 32-разрядном режиме выпуска с оптимизацией /O 2 и запущен на Vista x64. Аналогичные результаты достигаются с помощью 64-битного тестового приложения.

Binary search...
           fill vector (for reference) :  0.27 s
                   array with ptr math :  0.38 s <-- C-style pointers lose
                  array with int index :  0.23 s <-- [] on raw array wins
            array with ptrdiff_t index :  0.24 s
                 vector with int index :  0.30 s  <-- small penalty for vector abstraction
           vector with ptrdiff_t index :  0.30 s

Counting memory (de)allocation...
                memset (for reference) :  2.85 s
      fill malloc-ed raw array with [] :  2.66 s
     fill malloc-ed raw array with ptr :  2.81 s
         fill new-ed raw array with [] :  2.64 s
        fill new-ed raw array with ptr :  2.65 s
                  fill vector as array :  3.06 s  \ something slower 
                           fill vector :  3.05 s  / with vector!

NOT counting memory (de)allocation...
                memset (for reference) :  2.57 s
      fill malloc-ed raw array with [] :  2.86 s
     fill malloc-ed raw array with ptr :  2.60 s
         fill new-ed raw array with [] :  2.63 s
        fill new-ed raw array with ptr :  2.78 s
                  fill vector as array :  2.49 s \ after discounting the  
                           fill vector :  2.54 s / (de)allocation vector is faster!

код:

#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#include <string>
#include <vector>
#include <stdio.h>

using namespace std;

__int64 freq; // initialized in main
int const N = 1024*1024*1024/sizeof(int)/2; // 1/2 GB of data
int const nIter = 10;

class Timer {
public:
  Timer(char *name) : name(name) {
    QueryPerformanceCounter((LARGE_INTEGER*)&start);
  }
  ~Timer() {
    __int64 stop;
    QueryPerformanceCounter((LARGE_INTEGER*)&stop);
    printf("  %36s : % 4.2f s\n", name.c_str(), (stop - start)/double(freq));
  }
private:
  string const name;
  __int64 start;
};


template <typename Container, typename Index>
int binarySearch_indexed(Container sortedArray, Index first, Index last, int key) {
  while (first <= last) {
    Index mid = (first + last) / 2; // NOT safe if (first+last) is too big!
    if (key > sortedArray[mid])      first = mid + 1;
    else if (key < sortedArray[mid])  last = mid - 1; 
    else return mid;  
  }
  return 0; // Use "(Index)-1" in real code
}

int Dummy = -1;
int const *binarySearch_ptr(int const *first, int const *last, int key) {
  while (first <= last) {
    int const *mid = (int const *)(((unsigned __int64)first + (unsigned __int64)last) / 2);  
    if (key > *mid)      first = mid + 1;
    else if (key < *mid)  last = mid - 1; 
    else return mid;  
  }
  return &Dummy; // no NULL checks: don't do this for real
}

void timeFillWithAlloc() {
  printf("Counting memory (de)allocation...\n");
  { 
    Timer tt("memset (for reference)");
    int *data = (int*)malloc(N*sizeof(int));
    for (int it=0; it<nIter; it++) memset(data, 0, N*sizeof(int));
    free(data);
  }
  { 
    Timer tt("fill malloc-ed raw array with []");
    int *data = (int*)malloc(N*sizeof(int));
    for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
    free(data);
  }
  { 
    Timer tt("fill malloc-ed raw array with ptr");
    int *data = (int*)malloc(N*sizeof(int));
    for (int it=0; it<nIter; it++) {
    int *d = data;
    for (size_t i=0; i<N; i++) *d++ = (int)i;
    }
    free(data);
  }
  { 
    Timer tt("fill new-ed raw array with []");
    int *data = new int[N];
    for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
    delete [] data;
  }
  { 
    Timer tt("fill new-ed raw array with ptr");
    int *data = new int[N];
    for (int it=0; it<nIter; it++) {
    int *d = data;
    for (size_t i=0; i<N; i++) *d++ = (int)i;
    }
    delete [] data;
  }
  { 
    Timer tt("fill vector as array");
    vector<int> data(N); 
    for (int it=0; it<nIter; it++) {
      int *d = &data[0]; 
    for (size_t i=0; i<N; i++) *d++ = (int)i;
    }
  }
  { 
    Timer tt("fill vector");
    vector<int> data(N); 
    for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
  }
  printf("\n");
}

void timeFillNoAlloc() {
  printf("NOT counting memory (de)allocation...\n");

  { 
    int *data = (int*)malloc(N*sizeof(int));
    {
      Timer tt("memset (for reference)");
      for (int it=0; it<nIter; it++) memset(data, 0, N*sizeof(int));
    }
    free(data);
  }
  { 
    int *data = (int*)malloc(N*sizeof(int));
    {
      Timer tt("fill malloc-ed raw array with []");
      for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
    }
    free(data);
  }
  { 
    int *data = (int*)malloc(N*sizeof(int));
    {
      Timer tt("fill malloc-ed raw array with ptr");
      for (int it=0; it<nIter; it++) {
        int *d = data;
        for (size_t i=0; i<N; i++) *d++ = (int)i;
      }
    }
    free(data);
  }
  { 
    int *data = new int[N];
    {
      Timer tt("fill new-ed raw array with []");
      for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
    }
    delete [] data;
  }
  { 
    int *data = new int[N];
    {
      Timer tt("fill new-ed raw array with ptr");
      for (int it=0; it<nIter; it++) {
        int *d = data;
        for (size_t i=0; i<N; i++) *d++ = (int)i;
      }
    }
    delete [] data;
  }
  { 
    vector<int> data(N); 
    {
      Timer tt("fill vector as array");
      for (int it=0; it<nIter; it++) {
        int *d = &data[0]; 
        for (size_t i=0; i<N; i++) *d++ = (int)i;
      }
    }
  }
  { 
    vector<int> data(N); 
    {
      Timer tt("fill vector");
      for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
    }
  }
  printf("\n");
}

void timeBinarySearch() {
  printf("Binary search...\n");
  vector<int> data(N); 
  {
    Timer tt("fill vector (for reference)");
    for (size_t i=0; i<N; i++) data[i] = (int)i;
  }

  {
    Timer tt("array with ptr math");
    int sum = 0;
    for (int i=-1000000; i<1000000; i++) {
      sum += *binarySearch_ptr(&data[0], &data[0]+data.size(), i);
    }
  }
  {
    Timer tt("array with int index");
    int sum = 0;
    for (int i=-1000000; i<1000000; i++) {
      sum += data[binarySearch_indexed<int const *, int>(
        &data[0], 0, (int)data.size(), -1)];
    }
  }
  {
    Timer tt("array with ptrdiff_t index");
    int sum = 0;
    for (int i=-1000000; i<1000000; i++) {
      sum += data[binarySearch_indexed<int const *, ptrdiff_t>(
        &data[0], 0, (ptrdiff_t)data.size(), -1)];
    }
  }
  {
    Timer tt("vector with int index");
    int sum = 0;
    for (int i=-1000000; i<1000000; i++) {
      sum += data[binarySearch_indexed<vector<int> const &, int>(
        data, 0, (int)data.size(), -1)];
    }
  }
  {
    Timer tt("vector with ptrdiff_t index");
    int sum = 0;
    for (int i=-1000000; i<1000000; i++) {
      sum += data[binarySearch_indexed<vector<int> const &, ptrdiff_t>(
        data, 0, (ptrdiff_t)data.size(), -1)];
    }
  }

  printf("\n");
}

int main(int argc, char **argv)
{
  QueryPerformanceFrequency((LARGE_INTEGER*)&freq);

  timeBinarySearch();
  timeFillWithAlloc();
  timeFillNoAlloc();

  return 0;
}

Ответ 4

Массив/char * полезен, когда производительность совместимости или имеет очень высокий приоритет. Векторы и строки - это объекты более высокого уровня, которые лучше, когда учитываются удобство обслуживания, читаемость и избыточная легкость кода. Почти всегда, то есть.

Ответ 5

Я предлагаю использовать массивы всякий раз, когда вы знаете размер самого времени компиляции. Хотя вектор можно использовать, мы должны помнить, что вектор имеет свои накладные расходы, связанные с распределением памяти, сделанным в куче. Если размер неизвестен, то, конечно, используйте векторы.

Ответ 6

Единственная причина, по которой я мог думать, - это скорость. Вы можете лучше оптимизировать типы массивов/указателей, чем для соответствующих объектов. Но я бы даже использовал STL, если бы я совершенно точно знал количество данных, которые должны хранить мои структуры данных. Переход от STL к примитивным типам на этапе оптимизации лучше, чем запуск проекта с более сложным для чтения кодом.

Ответ 7

Я вижу две причины:

  • Совместимость (старый код без STL).
  • Скорость. (Я сравнил скорость использования vector/binary_search и массив/рукописный двоичный поиск. Для последнего был получен уродливый код (с перераспределением памяти), но он был примерно в 1,2-1,5 раза быстрее, чем первый, я использовал MS VС++ 8 )

Ответ 8

Я работаю над общей библиотекой, которая нуждается в доступе к структурированным данным. Эти данные известны во время компиляции, поэтому для хранения данных используются константные массивы с файловыми структурами структур POD (простые старые данные).

Это заставляет компилятор и компоновщик помещать большую часть данных в раздел только для чтения с двумя преимуществами:

  • Он может быть отображен в каталог памяти с диска без запуска какого-либо специального кода инициализации.
  • Он может быть разделен между процессами.

Единственное исключение состоит в том, что компилятор все еще генерирует код инициализации для загрузки констант с плавающей запятой, поэтому любая структура, содержащая числа с плавающей запятой, заканчивается в разделе, доступном для записи. Я подозреваю, что это что-то делает с плавающими исключениями или режимами округления с плавающей запятой, но я не уверен, как проверить любую гипотезу.

Если для этого я использовал векторные и строковые объекты, тогда компилятор будет генерировать намного больше кода инициализации, который будет выполняться всякий раз, когда загружается моя разделяемая библиотека. Постоянные данные будут распределены по куче, поэтому он не будет использоваться между процессами.

Если я прочитаю данные из файла на диске, мне придется иметь дело с проверкой формата данных, вместо того, чтобы компилятор С++ сделал это для меня. Я также должен был бы управлять временем жизни данных в кодовой базе, которая с самого начала имела эти глобальные данные, "запеченные" в ней.