【问题标题】:When would you use an array rather than a vector/string?你什么时候会使用数组而不是向量/字符串?
【发布时间】:2010-10-10 07:41:10
【问题描述】:

我是一名初学者 C++ 程序员,因此我学会了使用数组而不是向量(这似乎是做事的一般方式,然后再转向向量)。

我注意到关于 SO 的很多答案都建议在数组上使用向量,在 char 数组上使用字符串。这似乎是用 C++ 编写代码的“正确”方式。

话虽如此,什么时候仍然值得使用经典的 array/char*(如果有的话)?

【问题讨论】:

    标签: c++ arrays string vector


    【解决方案1】:

    在编写应在其他项目中使用的代码时,尤其是当您针对可能不存在 STL 的特殊平台(嵌入式、游戏机等)时。

    旧项目或有特殊要求的项目可能不想引入对 STL 库的依赖。一个依赖于数组、char* 或任何与任何东西兼容的接口,因为它是语言的一部分。但是,不能保证 STL 存在于所有构建环境中。

    【讨论】:

      【解决方案2】:

      从不。

      如果原始数组似乎比向量更好的解决方案(出于此处所述的其他原因),那么我在 C++11 编译器中使用 std::tr1::array 或 std::array (或boost::array)。 它只是做我无论如何都会做的检查以确保大小值的使用使 DRY 自动实现(例如,我在循环中使用大小,以便将来对数组声明的更改将自动起作用)。

      它的数组实现“是”一个带有检查和提供大小常量的原始数组,因此也很容易在嵌入式代码中获取数组代码,因为 the code is not really "too smart" 对于任何编译器。至于编译器支持模板,我会在我的代码中复制 boost 标头,以允许我使用这个而不是原始数组。因为使用原始数组显然太容易出错了。 Raw arrays are evil. 他们容易出错。

      它与 STL 算法(如果可用)配合得非常好。

      现在,有两种情况您需要使用原始数组(义务):当您在纯 C 代码中时(不与 C 代码通信,而是在纯 C 部分编写代码,如 C 库)。但它是另一种语言

      另一个原因是编译器根本不支持模板...

      【讨论】:

      • 我非常同意。与 boost::array 类似的东西对于原始 STL 来说应该是不费吹灰之力的;我一直不明白为什么不包括在内(除非整数模板参数是后来添加到语言中的?)
      • 是的,他们似乎试图用 TR1 解决这个问题,但编译器的速度不够快,无法让它流行起来。 :/ 无论如何,今天当 std::tr1::array 或 boost::array 是可编译的时,没有理由使用原始数组。我猜 C++0x 将有助于以后使其成为“标准方式”。
      【解决方案3】:

      这个问题实际上可以分为两部分:

      1. 我应该如何管理平面数组数据的内存?
      2. 我应该如何访问平面数组的元素?

      我个人更喜欢使用 std::vector 来管理内存,除非我需要保持与不使用 STL 的代码的兼容性(即与直接 C 代码接口)。使用通过 new 或 malloc 分配的原始数组来编写 异常安全 代码要困难得多(部分原因是很容易忘记您需要担心它)。原因见RAII上的任何文章。

      实际上,std::vector 被实现为平面数组。因此,始终可以提取原始数组并使用 C 风格的访问模式。我通常从向量下标运算符语法开始。对于某些编译器,在生成调试版本时,向量会提供自动边界检查。这很慢(对于紧密循环,通常会减慢 10 倍),但有助于发现某些类型的错误。

      如果在特定平台上的分析表明 operator[] 是一个瓶颈,那么我切换到直接访问原始数组。有趣的是,根据编译器和操作系统的不同,有时使用 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;
      }
      

      【讨论】:

      • std::vector 不保证是标准的平面数组,这让很多 C++ 标准委员会成员在意识到这一点时感到惊讶。我不知道任何不包含平面数组的实现,即将到来的 C++ 标准将保证这一点。
      【解决方案4】:
      当兼容性性能非常重要时,

      Array/char* 很有用。向量和字符串是更高级别的对象,在代码可维护性、可读性和整体易用性方面表现更好。几乎总是这样。

      【讨论】:

        【解决方案5】:

        我建议在编译时知道大小时使用数组。尽管可以使用向量,但我们必须记住,向量的开销与在堆上完成的内存分配有关。如果大小未知,那么当然使用向量。

        【讨论】:

        • 你也可以使用 boost::array 或 tr1::array。
        • 复制开销与数组有何不同?我能想到数组效率更高的唯一情况是,如果你有 "Foo arr[] = {Foo(...),Foo(...),...};"
        • @Mr Fooz:是的,你是对的..没有区别。我编辑了我的答案。
        • 还要记住,数组对于具有非平凡析构函数的数据类型是危险的。除非你真的非常需要,否则永远不要使用它们的数组。
        • 我可以想到我知道大小的情况,但是使用数组而不是vector 是错误的。第一个例子,大小是 5000 万。我没有那么多筹码。第二个例子,我想要一个快速或不抛出 swap 操作 - 向量有,而数组没有(不仅仅是因为 std::swap 不适用于数组,你也不能编写自己的快速数组交换,或者 nothrow交换类型数组而没有自己的 nothrow 交换)。所以有时我会使用vector,尽管知道大小。
        【解决方案6】:

        我能想到的唯一原因就是速度。与相应的对象相比,您可以对数组/指针类型进行更好的优化。但如果我绝对知道我的数据结构需要保存的数据量,我什至会使用 STL。在优化步骤中从 STL 更改为原始类型比使用难以阅读的代码开始项目要好。

        【讨论】:

        • 好点。从我所见,英特尔 c++ 编译器在我们的 C 代码中输出的 LOOP WAS VECTORIZED 往往比我们的 c++ 代码(后者使用大量 STL)更多。
        • std::vector 保证使用平面数组进行存储。你总是可以对它们进行所有 C 风格的指针运算。
        【解决方案7】:

        我看到两个原因:

        1. 兼容性(没有 STL 的旧代码)。
        2. 速度。 (我比较了使用vector/binary_search & array/handwritten binary search的速度。对于最后一个获得了丑陋的代码(重新分配内存),但它比第一个快1.2-1.5倍,我使用MS VC++ 8 )

        【讨论】:

          【解决方案8】:

          我在一个需要访问结构化数据的共享库上工作。此数据在编译时是已知的,因此它使用 POD(普通旧数据)结构的文件范围常量数组来保存数据。

          这会导致编译器和链接器将大部分数据放在只读部分中,有两个好处:

          • 它可以从磁盘映射到内存目录,而无需运行任何特殊的初始化代码。
          • 它可以在进程之间共享。

          唯一的例外是编译器仍然会生成初始化代码来加载浮点常量,因此任何包含浮点数的结构最终都会进入可写部分。我怀疑这与浮点异常或浮点舍入模式有关,但我不确定如何验证这两种假设。

          如果我为此使用向量和字符串对象,那么编译器将生成更多的初始化代码,这些初始化代码将在我的共享库加载时执行。常量数据将分配在堆上,因此进程之间不可共享。

          如果我从磁盘上的文件中读取数据,我将不得不检查数据的格式,而不是让 C++ 编译器为我做这件事。我还必须管理代码库中数据的生命周期,该代码库从一开始就将全局数据“烘焙”到其中。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2010-09-23
            • 2020-01-10
            • 2012-08-20
            • 2010-11-16
            • 1970-01-01
            • 1970-01-01
            • 2012-06-07
            相关资源
            最近更新 更多