代码之家  ›  专栏  ›  技术社区  ›  Akusete

在空std::vector上使用运算符[]

  •  8
  • Akusete  · 技术社区  · 15 年前

    不久前有人告诉我那是一个很普通的地方 STD::向量作为C++中异常安全的动态数组 而不是分配原始数组…例如

    {
        std::vector<char> scoped_array (size);
        char* pointer = &scoped_array[0];
    
        //do work
    
    } // exception safe deallocation
    

    我已经多次使用这个约定,没有问题,但是最近 将一些代码移植到win32 VisualStudio2010(以前它只在MacOS/Linux上)和我的 当向量大小恰好为零时,单元测试正在中断(stdlib抛出断言) .

    我知道写入这样一个数组是一个问题,但是这个假设破坏了这个解决方案作为原始指针的替换。考虑以下函数 n=0

    void foo (int n) {
       char* raw_array = new char[n];
       char* pointer = raw_array;
       file.read ( pointer , n );
       for (int i = 0; i < n; ++i) {
          //do something
       }
       delete[] raw_array;
    }
    

    虽然可以说是多余的,但上面的代码是完全合法的(我相信),而下面的代码将在VisualStudio2010上声明

    void foo (int n) {
       std::vector<char> scoped_array (n);
       char* pointer = &scoped_array[0];
       file.read ( pointer , n );
       for (int i = 0; i < n; ++i) {
      //do something
       }
    }
    

    我一直在使用未定义的行为吗?我觉得操作员[]没有进行错误检查,这是对std::vector<>的有效使用。还有人遇到过这个问题吗?

    ——编辑: 感谢所有有用的回答,回答人们说这是未定义的行为。 是否有方法替换上面的原始数组分配 n=0 ?

    一边说,一边检查 n=0 作为一个例外情况,它将解决这个问题。有许多模式不需要特殊情况(如上面的原始指针示例),因此可能需要使用std::vector<>以外的其他类型?

    7 回复  |  直到 15 年前
        1
  •  8
  •   MSalters    15 年前

    LWG issue 464 . 这是一个已知的问题。 C++0x(它由MSVC 2010部分实现)通过添加 .data() 成员。

        2
  •  4
  •   Steve Jessop    15 年前

    就C++标准而言, operator[] 不能保证不检查,只是那样(不像 at() )不保证检查。

    在不检查的实现中, &scoped_array[scoped_array.size()] 将导致在向量分配的数组的末尾之内或之外有一个合法指针。这不是明确保证的,但是对于给定的实现,您可以通过查看其源代码来进行验证。对于一个空向量,可能根本没有分配(作为优化),我在 vector 标准的一部分,定义了 scoped_array[0] 表68除外。

    从表68可以看出,表达式的结果是 &*(a.begin() + 0) ,它非法取消引用非结束迭代器。如果您的实现的向量迭代器只是一个指针,那么您可能就不需要这样做了——如果不是的话,您可能不需要,显然您的不是。

    我忘记了关于是否 &* 在不可取消引用的指针上,是“否”或“否”。iIRC标准中的规定不明确(有些模棱两可),这引发了修改标准以使其明确合法的要求。这表明它实际上在所有或最著名的实现上都有效。

    就我个人而言,我不会依赖这一点,我也不会取消检查。我重写了你的代码:

    char* pointer = (scoped_array.size() > 0) ? &scoped_array[0] : 0;
    

    或者在这种情况下只是:

    char* pointer = (n > 0) ? &scoped_array[0] : 0;
    

    在不知道大小至少为n+1的情况下使用向量的索引n对我来说是错误的,不管它在禁用检查后是否在您的实现中实际工作。

        3
  •  1
  •   Paul Groke Chronial    15 年前

    operator [] 返回一个引用,因此在空向量上调用它必须是未定义的。

    毕竟,当没有项目时,引用应该引用哪个项目? 操作员[ ] 必须返回空引用或完全无效的引用。这两者都会导致未定义的行为。

    所以是的,你一直在使用未定义的行为。Visual Studio不是强制的,但仍有形式的签入 操作员[ ] 刚刚揭露了这个事实。

        4
  •  0
  •   Yakov Galka    15 年前

    MVS进行范围检查 operator[] 即使在发布版本中。我不知道它是否符合标准。(我在它们的实现中发现了调试代码,这使得它们的实现破坏了正确的代码)。但是有一个开关可以禁用它。

        5
  •  0
  •   Community Mohan Dere    9 年前

    这使我想起了一个有趣的问题,我立即问了这个问题。 here . 在您的情况下,可以避免以下方式使用指针:

    template<class InputIterator, class OutputIterator>
    OutputIterator copy_n( InputIterator first, InputIterator last, OutputIterator result, std::size_t n)
    {
        for ( std::size_t i = 0; i < n; i++ ) {
            if (first == last)
                break;
            else
                *result++ = *first++;
        }
        return result;
    }
    
    std::ifstream file("path_to_file");
    std::vector<char> buffer(n);
    copy_n(std::istream_iterator<char>(file), 
           std::istream_iterator<char>(),
           std::back_insert_iterator<vector<char> >(buffer),
           n);
    

    这将把文件的内容复制到缓冲区 n 一次一个字符。在缓冲区上迭代时,请使用:

    for (std::vector<char>::iterator it = buffer.begin(); it != buffer.end(); it++)
    

    而不是计数器。

        6
  •  0
  •   Steve Townsend    15 年前

    如果您希望在这种情况下获得更干净的行为,可以替换使用 a[0] 用用 a.at(0) ,如果索引无效,将引发。

    一个实用的解决方案是使用n+1条目初始化向量,并将访问限制为0..n-1(正如这段代码已经做的那样)。

    void foo (int n) {
       std::vector<char> scoped_array (n+1);
       char* pointer = &scoped_array[0];
       file.read ( pointer , n );
       for (int i = 0; i < n; ++i) {
      //do something
       }
    }
    
        7
  •  0
  •   Arne    15 年前

    你能用迭代器代替指针吗?

    {
        std::vector<char> scoped_array (size);
        std::vector<char>::iterator pointer = scoped_array.begin();
    
        //do work
    
    } // exception safe deallocation
    
    推荐文章