2010-04-26 33 views
7

Tôi cần phải loại bỏ các phần tử ở giữa một std :: vector.Hành vi lạ với vector :: xóa và std :: remove_if với phạm vi kết thúc khác với vector.end()

Vì vậy, tôi đã cố gắng:

struct IsEven { 
    bool operator()(int ele) 
    { 
     return ele % 2 == 0; 
    } 
}; 

    int elements[] = {1, 2, 3, 4, 5, 6}; 
    std::vector<int> ints(elements, elements+6); 

    std::vector<int>::iterator it = std::remove_if(ints.begin() + 2, ints.begin() + 4, IsEven()); 
    ints.erase(it, ints.end()); 

Sau này, tôi hy vọng rằng các vector ints có: [1, 2, 3, 5, 6].

Trong trình gỡ rối của Visual studio 2008, sau dòng std::remove_if, các phần tử của ints được sửa đổi, tôi đoán tôi đang ở trong một số hành vi không xác định tại đây.

Vì vậy, làm cách nào để xóa các phần tử khỏi Phạm vi vectơ?

Trả lời

13

Chỉnh sửa: Xin lỗi, phiên bản gốc của điều này là không chính xác. Đã sửa.

Đây là những gì đang diễn ra. đầu vào của bạn để remove_if là:

1 2 3 4 5 6 
    ^ ^
    begin end 

Và thuật toán remove_if nhìn vào tất cả các số giữa beginend (bao gồm begin, nhưng không bao gồm end), và loại bỏ tất cả các yếu tố giữa trận đấu mà vị ngữ của bạn. Vì vậy, sau remove_if chạy, vector của bạn trông như thế này

1 2 3 ? 5 6 
    ^^ 
    begin new_end 

đâu ? là một giá trị mà tôi không nghĩ là xác định, mặc dù nếu nó đảm bảo được bất cứ điều gì nó sẽ là 4. Và new_end, trỏ đến kết thúc mới của chuỗi đầu vào bạn đã cung cấp cho nó, với các yếu tố phù hợp đã bị xóa, là số được trả lại bởi std::remove_if. Lưu ý rằng std::remove_if không chạm vào bất kỳ thứ gì ngoài chuỗi mà bạn đã cung cấp. Điều này có thể có ý nghĩa hơn với một ví dụ mở rộng hơn.

nói rằng đây là đầu vào của bạn:

1 2 3 4 5 6 7 8 9 10 
    ^   ^
    begin   end 

Sau std::remove_if, bạn nhận được:

1 2 3 5 7 ? ? 8 9 10 
    ^ ^
    begin  new_end 

nghĩ về điều này trong một khoảnh khắc. Những gì nó đã làm là loại bỏ 4 và 6 từ sau đó, và sau đó thay đổi tất cả mọi thứ trong phần sau xuống để điền vào các phần tử đã xóa và sau đó di chuyển trình lặp end đến đầu mới của cùng một chuỗi. Mục tiêu là để đáp ứng yêu cầu rằng trình tự (begin, new_end] mà nó tạo ra giống với chuỗi (begin, end] mà bạn đã chuyển vào, nhưng với một số thành phần nhất định bị xóa. Mọi thứ ở hoặc bên ngoài end mà bạn đã chuyển được đụng đến nó.

gì bạn muốn để có được thoát khỏi, sau đó, là tất cả mọi thứ giữa iterator cuối đã được trả lại, và iterator cuối ban đầu mà bạn đã cho nó. Đây là những ? "rác" giá trị. Vì vậy, cuộc gọi xóa của bạn thực sự phải là:

ints.erase(it, ints.begin()+4); 

Cuộc gọi đến erase mà bạn vừa xóa mọi thứ ngoài phần cuối của chuỗi mà bạn đã thực hiện việc xóa, đó không phải là những gì bạn muốn ở đây.

Điều phức tạp này là thuật toán remove_if không thực sự gọi erase() trên véc tơ, hoặc thay đổi kích thước của véc-tơ tại bất kỳ thời điểm nào. Nó chỉ thay đổi các phần tử xung quanh và để lại một số phần tử "rác" sau khi kết thúc chuỗi mà bạn yêu cầu nó xử lý. Điều này có vẻ ngớ ngẩn, nhưng toàn bộ lý do STL thực hiện theo cách này là tránh vấn đề với các trình vòng lặp không hợp lệ mà doublep đã đưa lên (và có thể chạy trên những thứ không phải là các container STL, như các mảng thô).

+0

Đồ họa đẹp :) Vâng, tôi không nghĩ rằng nó là cần thiết để bỏ qua cuộc gọi erase(), nhưng trình gỡ lỗi cho tôi một số hành vi kỳ lạ sau remove_if(), vì vậy tôi đã tự hỏi nếu tôi đã làm điều gì đó sai hoặc loại sử dụng này không chính xác (sử dụng danh sách sau đó, như doublep đã nói) –

+0

Rất tốt, phiên bản mở rộng của bạn thực sự xóa mọi thứ cho tôi. Tôi sẽ thử nghiệm các đề xuất của bạn và sớm đưa ra phản hồi –

1

Xóa các phần tử trong std::vector làm mất hiệu lực trình vòng lặp qua phần tử đã xóa, do đó bạn không thể sử dụng các hàm "ngoại lai" chấp nhận phạm vi. Bạn cần phải làm điều đó theo một cách khác.

EDIT:

Nói chung, bạn có thể sử dụng thực tế là xóa một yếu tố "chuyển" tất cả các yếu tố tại các vị trí xa hơn một trở lại. Một cái gì đó như thế này:

for (size_t scan = 2, end = 4; scan != end;) 
    { 
    if (/* some predicate on ints[scan] */) 
     { 
     ints.erase (ints.begin() + scan); 
     --end; 
     } 
    else 
     ++scan; 
    } 

Lưu ý rằng std::vector là không thích hợp cho xóa các yếu tố ở giữa. Bạn nên cân nhắc điều gì khác (std::list?) Nếu bạn thường xuyên làm điều đó.

CHỈNH SỬA 2:

Được làm rõ bằng các nhận xét, đoạn đầu tiên không đúng. Trong trường hợp này, std::remove_if sẽ hiệu quả hơn những gì tôi đề xuất trong lần chỉnh sửa đầu tiên, vì vậy hãy bỏ qua câu trả lời này. (Giữ nó cho các ý kiến.)

+0

Er, không, hoàn toàn an toàn khi sử dụng 'std :: remove_if' trên' std :: vector'. STL đủ thông minh để tránh sử dụng các trình vòng lặp không hợp lệ khi thực hiện điều đó. –

+0

@Tyler: Bạn có chắc chắn không?Tôi không thấy bất kỳ chuyên môn nào có liên quan ít nhất trong GNU STL và hàm chuẩn không bao giờ sửa đổi đối số 'cuối cùng 'của nó. – doublep

+1

@Tyler, có nó là hoàn toàn an toàn, nhưng nó không có gì để làm với STL được thông minh. 'std :: remove_if' chỉ đơn giản là sao chép các phần tử về phía trước để đạt được hiệu quả của việc loại bỏ các phần tử mà chúng thay thế; nó không làm gì cả với chính vectơ hoặc bất kỳ trình lặp nào có nguồn gốc từ nó. Trên thực tế, đuôi của vectơ không bị ảnh hưởng và chứa các phần tử không thuộc danh sách giảm. Để loại bỏ chúng, bạn phải cắt bớt véc tơ bằng cách sử dụng trình lặp được trả về từ 'std :: remove_if'. –

1

Hành vi không lạ - bạn đang xóa sai phạm vi. std::remove_if di chuyển các phần tử nó "xóa" vào cuối phạm vi nhập liệu. Trong trường hợp này, những gì bạn đang tìm kiếm sẽ được thực hiện:

ints.erase(it, ints.begin() + 4 /* your end of range */); 

Từ C++ in a Nutshell:

Chức năng remove_if mẫu "loại bỏ" các mục mà pred lợi nhuận sai từ phạm vi [đầu tiên, cuối cùng). Giá trị trả lại là một trong quá khứ mới kết thúc của dải ô. Thứ tự tương đối của các mục không bị xóa là ổn định.

Không có gì thực sự bị xóa khỏi vùng chứa cơ bản ; thay vào đó, các mục ở bên phải được gán cho các vị trí mới để chúng ghi đè các phần tử mà trước đó trả về sai. Xem Hình 13-13 (trong remove_copy) để biết ví dụ về quy trình xóa.

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