Когда бы вы использовали массив, а не вектор / строку? - PullRequest
11 голосов
/ 27 февраля 2009

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

Я заметил, что во многих ответах по SO предлагается использовать векторы над массивами и строки над массивами символов. Кажется, что это «правильный» способ написания кода на C ++.

Это все, как говорится, когда все еще стоит использовать классический массив / символ * (если вообще)?

Ответы [ 8 ]

17 голосов
/ 27 февраля 2009

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

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

15 голосов
/ 27 февраля 2009

Никогда.

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

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

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

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

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

6 голосов
/ 27 февраля 2009

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

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

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

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

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

Вот некоторые результаты простого тестового приложения. Он был скомпилирован с Visual Studio 2008 в 32-разрядном режиме выпуска с использованием оптимизации / O2 и запущен в 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's 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 голосов
/ 27 февраля 2009

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

3 голосов
/ 27 февраля 2009

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

1 голос
/ 27 февраля 2009

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

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

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

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

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

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

1 голос
/ 27 февраля 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...