2009-02-27 17 views
11

Soy un programador de C++ para principiantes, así que aprendí a usar matrices en lugar de vectores (esta parece ser la manera general de hacer las cosas, luego paso a los vectores más adelante).¿Cuándo usarías una matriz en lugar de un vector/cadena?

He notado que muchas respuestas en SO sugieren utilizar vectores sobre matrices y cadenas sobre matrices char. Parece que esta es la forma "correcta" de codificar en C++.

Dicho todo esto, ¿cuándo vale la pena utilizar un array/char * clásico (si es que lo tiene)?

Respuesta

17

Cuando se escribe código que debería usarse en otros proyectos, en particular si se dirige a plataformas especiales (integradas, consolas de juegos, etc.) donde STL podría no estar presente.

Es posible que los proyectos o proyectos antiguos con requisitos especiales no deseen introducir dependencias en las bibliotecas STL. Una interfaz que depende de las matrices, char * o lo que sea, será compatible con cualquier cosa, ya que es parte del lenguaje. Sin embargo, no se garantiza que STL esté presente en todos los entornos de compilación.

3

Sugiero usar arreglos cada vez que sepa el tamaño en el momento de la compilación. Aunque se puede usar vector, debemos recordar que el vector viene con su sobrecarga relacionada con la asignación de memoria realizada en el montón. Si no se conoce el tamaño, entonces por supuesto usar vectores.

+0

También podría usar boost :: array o tr1 :: array. – Anonymous

+0

¿En qué se diferencia la copia de sobrecarga de una matriz? La única situación en la que puedo pensar donde las matrices serían más eficientes es si tuvieras "Foo arr [] = {Foo (...), Foo (...), ...};" –

+0

@Mr Fooz: Sí, tienes razón ... no hay diferencia. Edité mi respuesta. – Naveen

4

Array/char * es útil cuando la compatibilidad o tiene una prioridad muy alta. Los vectores y las cadenas son objetos de nivel superior que son mejores cuando cuentan la facilidad de mantenimiento del código, la legibilidad y la facilidad general. Casi siempre, eso es.

1

La única razón por la que podría pensar sería en la velocidad. Puede hacer mejores optimizaciones en los tipos de matriz/puntero que en los objetos correspondientes. Pero incluso usaría el STL si supiera absolutamente la cantidad de datos que mis estructuras de datos necesitan contener. Cambiar de STL a tipos primitivos en un paso de optimización es mejor que comenzar el proyecto con un código más difícil de leer.

+0

Buen punto. Por lo que he visto, el compilador de intels C++ tiende a mostrar más LOOP FUE VECTORIZADO en nuestro código C que nuestro código C++ (donde este último usa muchos STL) – Laserallan

+0

std :: vector garantiza el uso de una matriz plana para el almacenamiento. Siempre puede hacer toda la aritmética del puntero al estilo C en ellos. –

1

Veo dos razones:

  1. Compatibility (código antiguo y sin STL).
  2. Velocidad. (Compare la velocidad de uso de vector/binary_search & array/búsqueda binaria manuscrita. Para el último código feo se obtuvo (con reasignación de memoria), pero fue aproximadamente 1.2-1.5 veces más rápido que el primero, utilicé MS VC++ 8)
15

Nunca.

Si una matriz prima parece una mejor solución que un vector (por razones de otro dicho aquí), entonces yo uso std :: TR1 :: array o std :: matriz en C++ 11 compiladores (o boost::array) Simplemente hace las comprobaciones que haría de todos modos para estar seguro y el uso del valor de tamaño hace que DRY se implemente automáticamente (por ejemplo, uso el tamaño en loops para que los cambios futuros de la declaración de matriz funcionen automáticamente).

Es la implementación de la matriz "es" una matriz en bruto con controles y constante de tamaño proporcionado de todos modos, por lo que es fácil obtener el código de matriz en código incrustado también porque the code is not really "too smart" para cualquier compilador. En cuanto a las plantillas de soporte del compilador, copiaría los encabezados de impulso en mi código para permitirme usar este en lugar de matrices sin formato. Porque es claramente muy fácil cometer errores con las matrices en bruto. Raw arrays are evil. Son propensos a errores.

Y funciona muy bien con algoritmos STL (si está disponible).

Ahora, hay dos casos en los que necesidad de utilizar matrices primas (obligación): cuando estás en C-único código (no se comunica con el código C, pero escribir en C-sólo una parte del código, al igual una biblioteca C). Pero entonces es otro idioma.

La otra razón es cuando el compilador no son compatibles con las plantillas en todos los ...

+0

Estoy muy de acuerdo. Algo muy parecido a boost :: array debería haber sido una obviedad para el STL original; Nunca entendí por qué uno no estaba incluido (a menos que los parámetros de la plantilla entera fueran una adición posterior al lenguaje) – timday

+0

Sí, parece que trataron de arreglarlo con TR1 pero los compiladores no lo lograron lo suficientemente rápido como para hacerlo popular. :/ De todos modos, hoy en día no hay ninguna razón para usar matrices en bruto cuando std :: tr1 :: array o boost :: array es compilable. C++ 0x lo ayudará más adelante a convertirlo en "la forma estándar", supongo. – Klaim

6

Esta pregunta en realidad podría ser separada en dos partes:

  1. ¿Cómo debo gestionar la memoria de datos de matriz plana ?
  2. ¿Cómo debo acceder a los elementos de una matriz plana?

Yo personalmente prefiero utilizar std :: vector para la gestión de la memoria, excepto en los casos en que necesito para mantener la compatibilidad con el código que no utiliza STL (es decir, cuando interfaz con el código C recta). Es mucho más difícil hacer código de excepción con matrices en bruto asignadas a través de nuevo o malloc (en parte porque es muy fácil olvidar que debe preocuparse por ello). Vea cualquier artículo en RAII por las razones.

En la práctica, std :: vector se implementa como una matriz plana. Como tal, siempre es posible extraer la matriz sin procesar y usar patrones de acceso al estilo C. Normalmente comienzo con la sintaxis del operador del subíndice vectorial. Para algunos compiladores, cuando se produce una versión de depuración, los vectores proporcionan una verificación de límite automática . Esto es lento (a menudo una ralentización de 10 veces para bucles apretados), pero útil para encontrar ciertos tipos de errores.

Si el perfilado en una plataforma en particular indica que el operador [] es un cuello de botella, entonces cambio a acceder directamente a la matriz sin procesar. Curiosamente, según el compilador y el sistema operativo, a veces puede ser más rápido utilizar un vector STL que un conjunto sin formato.

Aquí hay algunos resultados de una aplicación de prueba simple. Se compiló con Visual Studio 2008 en modo de lanzamiento de 32 bits utilizando optimizaciones de/O2 y se ejecutó en Vista x64. Se obtienen resultados similares con una aplicación de prueba de 64 bits.

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! 

Código:

#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; 
} 
+0

std :: vector no se garantiza que sea una matriz plana según el estándar, lo que sorprendió a muchos miembros del Comité de estándares de C++ cuando se dieron cuenta. No conozco ninguna implementación que no contenga una matriz plana, y el próximo estándar de C++ lo garantizará. –

+0

@David: Interesante. La publicación ha sido actualizada. –

1

Yo trabajo en una biblioteca compartida que necesita tener acceso a los datos estructurados. Estos datos se conocen en tiempo de compilación, por lo que utiliza arrays de ámbito de archivo constante de estructuras POD (datos antiguos simples) para contener los datos.

Esto hace que el compilador y enlazador para poner la mayor parte de los datos en una sección de sólo lectura, con dos ventajas:

  • Se puede mapear en el directorio de memoria desde el disco sin ejecutar ningún código especial de inicialización.
  • Se puede compartir entre procesos.

La única excepción es que el compilador aún genera código de inicialización para cargar constantes de punto flotante, por lo que cualquier estructura que contenga números de punto flotante termina en una sección de escritura. Sospecho que esto tiene algo que ver con las excepciones flotantes o los modos de redondeo de punto flotante, pero no estoy seguro de cómo verificar ninguna hipótesis.

Si utilicé vectores y objetos de cadena para esto, entonces el compilador generaría mucho más código de inicialización que se ejecutaría siempre que mi biblioteca compartida esté cargada. La información constante se asignaría en el montón, por lo que no sería compartible entre procesos.

Si leo los datos desde un archivo en disco, tendría que encargarme de verificar el formato de los datos, en lugar de hacer que el compilador C++ lo haga por mí. También tendría que administrar la vida útil de los datos en una base de código que ha tenido estos datos globales "integrados" desde el principio.

Cuestiones relacionadas