2010-09-30 70 views
7

前段时间,我建议我使用std :: vector作为C++中的异常安全动态数组,而不是分配原始数组......例如在空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; 
} 

虽然可以说是多余的,上面的代码完全合法(I相信),而下面的代码将抛出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 <以外的东西?

+1

抛出哪个异常?我没有看到代码错误。 – fschmitt 2010-09-30 10:45:00

+2

@fschmitt:如果向量为空,那么'scoped_array [0]'给出未定义的行为。在这种情况下,使用Visual C++构建一个调试变体,它会失败范围检查并引发异常。 – 2010-09-30 11:28:15

+0

@fschmitt:std :: vector的visual studio实现会触发一个断言(即立即终止整个过程),并显示一个错误,说明向量上出现了一个越界错误。 – Akusete 2010-09-30 11:33:16

回答

8

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

+0

.data()将是完美的,太糟糕了,它只在C++ 0x – Akusete 2010-09-30 11:58:49

+0

我接受这个答案只是因为它引用讨论为什么这种类型的操作是想要的,什么将(最终)成为一个标准方法来做到这一点。 – Akusete 2010-09-30 12:05:01

0

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

+0

这不是每个标准,但它非常有助于快速调试未完成的代码。不幸的是,这导致需要调整一系列宏以获得所需的行为 - 我不知道在更高版本(VC9/VC10)中这是否是更清洁的。 – 2010-09-30 11:08:18

+0

@Steve:你的意思是标准*禁止*检查'operator []'使'&vec [0]'始终有效吗?你有参考书面的地方吗?编辑:没关系,Cubbi回答了这个问题。 – ybungalobill 2010-09-30 11:17:03

0

如果你想在这种情况下获得更清洁的行为,你可以使用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 
    } 
} 
+0

虽然这与他真正想要的相反。他希望不会抛出,就像数组/指针代码不会抛出一样。 – 2010-09-30 11:23:36

+0

@Steve Jessop - 我将目标读为'C++中的异常安全动态数组'。代码将代表(最好)和错误(更典型)。 – 2010-09-30 11:30:01

+0

就示例代码而言,我认为关键字是* array *。一个数组提供了一个过去的结束指针,只要读取的字节数为0,就可以合法地传递给'read'。一个向量不会给你那个指针,只是一个过去的指针-end * iterator *。所以在这方面不是一个异常安全的动态数组。我认为Akusete的矢量代码将会断言或通常工作。我不太清楚它的实现是如何实现的,尽管我们确信它是可以实现的。 – 2010-09-30 11:35:28

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++) 

而不是计数器。

4

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

你会期望在一个非检查实现中,&scoped_array[scoped_array.size()]会产生一个合法的指针,该指针位于该向量所分配的数组的内部或一个尾部。这并没有明确的保证,但对于给定的实现,您可以通过查看其来源进行验证。对于空向量,可能根本没有分配(作为优化),并且我没有看到scoped_array[0]的其他结果标准的vector部分中的任何内容。

从表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,无论它是否实际上在你的实现中起作用,一旦你禁用了检查。

+0

我同意,更好地重写此代码比尝试解决检查,它在那里是有原因的。 – 2010-09-30 11:31:49

1

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

毕竟,哪个项目应该引用参考,当没有项目? operator []将不得不返回一个空引用或一个完全无效的引用。这两者都会导致未定义的行为。

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

0

你可以使用迭代器而不是指针?

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

    //do work 

} // exception safe deallocation 
+0

一般来说不,我正在寻找一种以异常安全的方式分配正常指针数组的方法(有点像数组的auto_ptr)。我的印象是,使用std :: vector来达到这个目的是很好的做法。 – Akusete 2010-09-30 14:25:32