2013-06-12 69 views
10

Tôi đã sử dụng phạm vi mới cho vòng lặp do chuẩn C++ 11 cung cấp và tôi đã đưa ra câu hỏi sau: giả sử rằng chúng ta lặp qua một vector<> sử dụng phạm vi dựa trên for và chúng tôi thêm một số phần tử vào cuối vectơ trong lần lặp này. Vì vậy, khi nào kết thúc vòng lặp?Thêm các phần tử vào một vector trong vòng lặp dựa trên dải C++ 11

Ví dụ, xem mã này:

#include <iostream> 
#include <vector> 
using namespace std; 
int main() { 
    vector<unsigned> test({1,2,3}); 
    for(auto &num : test) { 
     cout << num << " "; 
     if(num % 2) 
      test.push_back(num + 10); 
    } 
    cout << "\n"; 
    for(auto &num : test) 
     cout << num << " "; 
    return 0; 
} 

Tôi đã thử nghiệm G ​​++ 4.8 và Apple LLVM phiên bản 4.2 (kêu vang ++) với "-std = C++ 11" cờ, và sản lượng là (cho cả hai) :

1 2 3 
1 2 3 11 13 

Lưu ý rằng vòng lặp đầu tiên chấm dứt vào cuối vector ban đầu, mặc dù chúng ta thêm các yếu tố khác với nó. Có vẻ như vòng lặp for-range chỉ đánh giá kết thúc của bộ chứa trong phần đầu. Đây có phải là hành vi chính xác của phạm vi? Nó có được chỉ định bởi ủy ban không? Chúng ta có thể tin tưởng vào hành vi này không?

Lưu ý rằng nếu chúng ta thay đổi vòng lặp đầu tiên bởi

for(vector<unsigned>::iterator it = test.begin(); it != test.end(); ++it) 

với không hợp lệ các vòng lặp và đưa ra một lỗi segmentation.

+0

mặc dù việc thêm không xóa, đây là bản sao của [Xóa một phần tử khỏi vùng chứa trong khi nằm trong phạm vi dựa trên phạm vi cho vòng lặp] (http://stackoverflow.com/questions/8624686/erasing-an-element- từ-một-container-trong-trong-một-dựa trên-cho-vòng lặp) bởi vì câu trả lời ở đó cho bạn thấy những gì tiêu chuẩn nói phạm vi cho không - kết thúc được xác định và lưu trước khi vòng lặp bắt đầu, và giá trị đã lưu được sử dụng trên các lần lặp tiếp theo. –

+1

@KateGregory Tôi cho rằng đây không phải là bản sao vì việc loại bỏ phần tử hiện tại sẽ là hành vi không xác định cho tất cả các thùng chứa, nhưng việc thêm phần tử chỉ là hành vi không xác định cho 'std :: vector',' std :: deque', 'std: : unordered_set' và 'std :: unorderd_map'. –

+0

Tôi dường như có câu hỏi đó, bởi hành vi này hơi khác một chút. Dù sao cũng cảm ơn. –

Trả lời

14

Không, bạn không thể dựa vào hành vi này. Sửa đổi vectơ bên trong vòng lặp kết quả trong hành vi không xác định bởi vì các vòng lặp được sử dụng bởi vòng lặp sẽ bị vô hiệu khi vector được sửa đổi.

Phạm vi dựa vòng lặp for

for (range_declaration : range_expression) loop_statement 

là về cơ bản tương đương với

{ 
    auto && __range = range_expression ; 
    for (auto __begin = std::begin(__range), 
     __end = std::end(__range); 
     __begin != __end; ++__begin) { 
      range_declaration = *__begin; 
      loop_statement 
    } 
} 

Khi bạn sửa đổi các vector, các vòng lặp __begin__end không còn giá trị và dereferencing __begin kết quả trong hành vi undefined .

+0

Cảm ơn David. Điều thú vị là hai đoạn mã của tôi, mặc dù về cơ bản là tương đương, có kết quả rất khác nhau. –

+2

trên thực tế, bạn đang thiếu các disctinction giữa cho (tự động _begin = r.begin();! _begin = r.end(); ++ bắt đầu) và cho (tự động _begin = r.begin() , _end = r.end(); _begin! = _end; ++ _ bắt đầu) (bộ đệm ẩn của r.end()) Và * đó là lý do tại sao chương trình của bạn có ấn tượng làm việc! trình vòng lặp đang trỏ đến phiên bản cũ của vectơ. Đặt một destructor trong các đối tượng chứa và hilarity sẽ xảy ra ... :-D – Massa

+0

_iterators được sử dụng bởi vòng lặp là vô hiệu khi vector được sửa đổi_ Không nhất thiết. Nếu dung lượng của vector đủ lớn để không yêu cầu phân bổ lại thì tất cả các trình vòng lặp vẫn hợp lệ. –

0

Bạn có thể dựa vào hành vi này miễn là dung lượng của vectơ đủ lớn, do đó không cần phải phân bổ lại. Điều này sẽ đảm bảo tính hợp lệ của tất cả các trình vòng lặp và tham chiếu khác với trình lặp vòng lặp kết thúc sau khi gọi tới push_back(). Điều này là tốt vì end() chỉ được tính một lần vào đầu vòng lặp (xem std::vector::push_back).

Trong trường hợp của bạn, bạn cần phải nâng cao năng lực của test vector để đảm bảo hành vi này:

vector<unsigned> test({1,2,3}); 
test.reserve(5); 

EDIT

Dựa trên phản ứng với Is it legal to add elements to a preallocated vector in a range-based for loop over that vector?, đây là một trường hợp hành vi không xác định. Phiên bản phát hành được xây dựng với gcc, clang và cl chạy tốt, nhưng một phiên bản gỡ lỗi được xây dựng với cl (Visual Studio) sẽ ném một ngoại lệ.

Các vấn đề liên quan