2010-09-30 54 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

相關問題