在空std :: vector上使用operator []

时间:2010-09-30 10:42:41

标签: c++ visual-studio-2010 vector

我刚才被告知,在 中使用 std :: vector作为异常安全动态数组是常见的,而不是分配原始数组...例如

{
    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
   }
}

我一直在使用未定义的行为吗?我的印象是operator []没有错误检查,这是std :: vector&lt;&gt;的有效使用。还有其他人遇到过这个问题吗?

- 编辑: 感谢所有有用的回复,回复人们说它是未定义的行为。 有没有办法替换上面可用的 n = 0 的原始数组分配?

虽然说检查 n = 0 作为例外情况将解决问题(它会)。有许多模式不需要特殊情况(例如上面的原始指针示例),所以可能使用除了std :: vector&lt;&gt;之外的其他东西。需要吗?

7 个答案:

答案 0 :(得分:8)

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

答案 1 :(得分:4)

就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而不知道大小至少为n + 1,这对我来说是错误的,无论一旦禁用检查,它是否真的适用于您的实现。

答案 2 :(得分:1)

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

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

所以是的,你一直在使用未定义的行为。 Visual Studio在operator []中进行的非强制性但仍然合理的检查只是揭示了这一事实。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

如果您希望在此方案中获得更清晰的行为,您可以将a[0]替换为使用a.at(0),如果索引无效,则会使用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 } }

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

{{1}}

答案 5 :(得分:0)

这给我带来了一个有趣的问题,我很快就问了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)

你能使用迭代器而不是指针吗?

{
    std::vector<char> scoped_array (size);
    std::vector<char>::iterator pointer = scoped_array.begin();

    //do work

} // exception safe deallocation