vector :: at so với vector :: operator []


95

Tôi biết điều đó at()chậm hơn so với []việc kiểm tra ranh giới của nó, điều này cũng được thảo luận trong các câu hỏi tương tự như C ++ Vector ở tốc độ toán tử / [] hoặc :: std :: vector :: at () vs operator [] << kết quả đáng ngạc nhiên !! Chậm hơn 5 đến 10 lần / nhanh hơn! . Tôi chỉ không hiểu at()phương pháp này tốt để làm gì.

Nếu tôi có một vectơ đơn giản như thế này: std::vector<int> v(10);và tôi quyết định truy cập các phần tử của nó bằng cách sử dụng at()thay vì []trong trường hợp tôi có chỉ mục ivà tôi không chắc liệu nó có bị giới hạn trong vectơ hay không, nó buộc tôi phải bọc nó bằng try-catch khối :

try
{
    v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
    ...
}

mặc dù tôi có thể thực hiện hành vi tương tự bằng cách tự mình sử dụng size()và kiểm tra chỉ mục, điều này có vẻ dễ dàng và thuận tiện hơn nhiều đối với tôi:

if (i < v.size())
    v[i] = 2;

Vì vậy, câu hỏi của tôi là:
Lợi ích của việc sử dụng vector :: at over vector :: operator [] là gì?
Khi nào tôi nên sử dụng vector :: at thay vì vector :: size + vector :: operator [] ?


11
+1 câu hỏi rất hay !! nhưng tôi không nghĩ tại () được sử dụng phổ biến.
Rohit Vipin Mathews

10
Lưu ý rằng trong mã ví dụ của bạn if (i < v.size()) v[i] = 2;, có thể có một đường dẫn mã hoàn toàn không gán 2cho bất kỳ phần tử nào v. Nếu đó là hành vi chính xác, tuyệt vời. Nhưng thường thì không có gì hợp lý khi chức năng này có thể làm được i >= v.size(). Vì vậy, không có lý do cụ thể nào tại sao nó không nên sử dụng một ngoại lệ để chỉ ra một tình huống bất ngờ. Nhiều chức năng chỉ sử dụng operator[]mà không cần kiểm tra kích thước, tài liệu iphải nằm trong phạm vi, và đổ lỗi UB kết quả cho người gọi.
Steve Jessop

Câu trả lời:


74

Tôi muốn nói rằng các trường hợp ngoại lệ vector::at()ném không thực sự có ý định bị bắt bởi mã xung quanh ngay lập tức. Chúng chủ yếu hữu ích để bắt lỗi trong mã của bạn. Nếu bạn cần kiểm tra giới hạn trong thời gian chạy vì ví dụ: chỉ mục đến từ đầu vào của người dùng, bạn thực sự nên sử dụng một ifcâu lệnh. Vì vậy, tóm lại, hãy thiết kế mã của bạn với ý định vector::at()không bao giờ có ngoại lệ, để nếu nó xảy ra và chương trình của bạn ngừng hoạt động, đó là dấu hiệu của một lỗi. (giống như một assert())


1
+1 Tôi thích lời giải thích về cách xử lý riêng biệt đối với đầu vào của người dùng sai (xác thực đầu vào; đầu vào không hợp lệ có thể được mong đợi nên không được coi là điều gì đó đặc biệt) ... và lỗi trong mã (trình lặp tham chiếu nằm ngoài phạm vi là đặc biệt điều)
Bojan Komazec

Vì vậy, bạn nói rằng tôi nên sử dụng size()+ []khi chỉ mục phụ thuộc vào người dùng nhập vào, sử dụng asserttrong các tình huống mà chỉ mục không bao giờ được vượt quá giới hạn để dễ dàng sửa lỗi trong tương lai và .at()trong tất cả các tình huống khác (đề phòng có thể xảy ra sự cố .. .)
LihO

8
@LihO: nếu triển khai của bạn cung cấp triển khai gỡ lỗi vectorthì có lẽ tốt hơn nên sử dụng tùy chọn đó làm tùy chọn "chỉ trong trường hợp" thay vì at()ở mọi nơi. Bằng cách đó, bạn có thể hy vọng hiệu suất cao hơn một chút trong chế độ phát hành, đề phòng trường hợp bạn cần.
Steve Jessop

3
Vâng, hầu hết các triển khai STL ngày nay đều hỗ trợ chế độ gỡ lỗi thậm chí có giới hạn kiểm tra operator[], ví dụ: gcc.gnu.org/onlineocs/libstdc++/manual/… vì vậy nếu nền tảng của bạn hỗ trợ điều này, có lẽ bạn nên sử dụng nó!
pmdj

1
@pmdj điểm tuyệt vời, mà tôi không biết về ... nhưng liên kết mồ côi. : P hiện tại là: gcc.gnu.org/onlineocs/libstdc++/manual/debug_mode.html
underscore_d

16

nó buộc tôi phải bọc nó bằng khối try-catch

Không, không có (khối try / catch có thể ngược dòng). Nó rất hữu ích khi bạn muốn một ngoại lệ được đưa ra thay vì chương trình của bạn vào vùng hành vi không xác định.

Tôi đồng ý rằng hầu hết các truy cập ngoài giới hạn vào vectơ là lỗi của lập trình viên (trong trường hợp đó, bạn nên sử dụng assertđể xác định các lỗi đó dễ dàng hơn; hầu hết các phiên bản gỡ lỗi của thư viện tiêu chuẩn tự động thực hiện việc này cho bạn). Bạn không muốn sử dụng các ngoại lệ có thể bị nuốt ngược dòng để báo cáo lỗi của lập trình viên: bạn muốn có thể sửa lỗi .

Vì không chắc rằng việc truy cập ngoài giới hạn vào một vectơ là một phần của luồng chương trình bình thường (trong trường hợp đúng như vậy, bạn nói đúng: hãy kiểm tra trước sizethay vì để ngoại lệ nổi lên), tôi đồng ý với chẩn đoán của bạn: atvề bản chất là vô dụng.


Nếu tôi không bắt được out_of_rangengoại lệ, thì abort()sẽ được gọi.
LihO 21/02/12

@LihO: Không nhất thiết..có try..catchthể có trong phương thức đang gọi phương thức này.
Naveen

12
Nếu không có gì khác, atsẽ hữu ích đến mức bạn sẽ thấy mình đang viết một cái gì đó giống như vậy if (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }. Mọi người thường nghĩ về các hàm ném ngoại lệ theo nghĩa "lời nguyền, tôi phải xử lý ngoại lệ", nhưng miễn là bạn ghi chép cẩn thận những gì mỗi hàm của bạn có thể ném ra, chúng cũng có thể được sử dụng như "tuyệt vời, tôi không phải kiểm tra một điều kiện và ném một ngoại lệ ".
Steve Jessop

@SteveJessop: Tôi không thích ném các ngoại lệ cho lỗi chương trình, vì chúng có thể bị các lập trình viên khác bắt ngược dòng. Các khẳng định hữu ích hơn nhiều ở đây.
Alexandre C.

6
@AlexandreC. tốt, phản hồi chính thức cho điều đó là out_of_rangebắt nguồn từ logic_error, và các lập trình viên khác "nên" biết tốt hơn là bắt kịp logic_errorvà bỏ qua chúng. assertcũng có thể bị bỏ qua nếu đồng nghiệp của bạn không muốn biết về lỗi của họ, điều đó chỉ khó hơn vì họ phải biên dịch mã của bạn với NDEBUG;-) Mỗi ​​cơ chế đều có ưu điểm và khuyết điểm.
Steve Jessop

11

Lợi ích của việc sử dụng vector :: at over vector :: operator [] là gì? Khi nào tôi nên sử dụng vector :: at thay vì vector :: size + vector :: operator []?

Điểm quan trọng ở đây là các ngoại lệ cho phép tách dòng mã bình thường khỏi logic xử lý lỗi và một khối bắt duy nhất có thể xử lý các vấn đề được tạo ra từ bất kỳ trang web ném nào, ngay cả khi nằm rải rác sâu bên trong các lệnh gọi hàm. Vì vậy, điều đó không at()nhất thiết là dễ dàng hơn cho một lần sử dụng, nhưng đôi khi nó trở nên dễ dàng hơn - và ít gây xáo trộn hơn đối với logic trường hợp bình thường - khi bạn có nhiều chỉ mục để xác thực.

Cũng cần lưu ý rằng trong một số loại mã, một chỉ mục đang được tăng dần theo những cách phức tạp và liên tục được sử dụng để tra cứu một mảng. Trong những trường hợp như vậy, sẽ dễ dàng hơn nhiều để đảm bảo kiểm tra chính xác bằng cách sử dụng at().

Như một ví dụ trong thế giới thực, tôi có mã mã hóa C ++ thành các phần tử từ vựng, sau đó mã khác di chuyển một chỉ mục qua vectơ mã thông báo. Tùy thuộc vào những gì gặp phải, tôi có thể muốn tăng và kiểm tra phần tử tiếp theo, như trong:

if (token.at(i) == Token::Keyword_Enum)
{
    ASSERT_EQ(tokens.at(++i), Token::Idn);
    if (tokens.at(++i) == Left_Brace)
        ...
    or whatever

Trong loại tình huống này, rất khó để kiểm tra xem bạn đã đến cuối đầu vào một cách không thích hợp hay chưa vì điều đó phụ thuộc rất nhiều vào các mã thông báo chính xác gặp phải. Kiểm tra rõ ràng tại mỗi điểm sử dụng là khó khăn và có nhiều chỗ cho lỗi của lập trình viên hơn như gia số trước / sau, hiệu số tại điểm sử dụng, lý luận sai lầm về tính hợp lệ liên tục của một số thử nghiệm trước đó, v.v. bắt đầu.


10

at có thể rõ ràng hơn nếu bạn có một con trỏ đến vectơ:

return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);

Hiệu suất sang một bên, điều đầu tiên trong số này là mã đơn giản và rõ ràng hơn.


... đặc biệt là khi bạn cần một con trỏ đến phần tử thứ n của một vectơ.
dolphin

4

Đầu tiên, cho dù at()hoặc operator[]là chậm không được xác định. Khi không có lỗi giới hạn, tôi mong đợi chúng có cùng tốc độ, ít nhất là trong các bản dựng gỡ lỗi. Sự khác biệt là at()chỉ định chính xác điều gì sẽ xảy ra khi có lỗi giới hạn (một ngoại lệ), trong trường hợp operator[]đó, đó là hành vi không xác định — một sự cố trong tất cả các hệ thống tôi sử dụng (g ++ và VC ++), ít nhất là khi cờ gỡ lỗi bình thường được sử dụng. (Một điểm khác biệt nữa là khi tôi chắc chắn về mã của mình, tôi có thể tăng tốc độ đáng kể operator[] bằng cách tắt gỡ lỗi. Nếu hiệu suất yêu cầu - tôi sẽ không làm điều đó trừ khi cần thiết.)

Trong thực tế, at()hiếm khi thích hợp. Nếu ngữ cảnh mà bạn biết rằng chỉ mục có thể không hợp lệ, bạn có thể muốn kiểm tra rõ ràng (ví dụ: trả về giá trị mặc định hoặc thứ gì đó) và nếu bạn biết rằng nó không thể không hợp lệ, bạn muốn hủy bỏ (và nếu bạn không biết liệu nó có thể không hợp lệ hay không, tôi khuyên bạn nên chỉ định giao diện chức năng của mình chính xác hơn). Tuy nhiên, có một số trường hợp ngoại lệ, trong đó chỉ mục không hợp lệ có thể do phân tích dữ liệu người dùng và lỗi sẽ gây ra việc hủy bỏ toàn bộ yêu cầu (nhưng không làm cho máy chủ hoạt động); trong những trường hợp như vậy, một ngoại lệ là phù hợp và at()sẽ làm điều đó cho bạn.


4
Tại sao bạn lại mong đợi chúng có cùng tốc độ, khi nào operator[]không bị buộc phải kiểm tra giới hạn, trong khi đó at()là? Bạn có đang ám chỉ các vấn đề liên quan đến bộ nhớ đệm, đầu cơ và phân nhánh không?
Sebastian Mach vào

@phresnel operator[]không bắt buộc phải kiểm tra giới hạn, nhưng tất cả các triển khai tốt đều có. Ít nhất là trong chế độ gỡ lỗi. Sự khác biệt duy nhất là những gì họ làm nếu chỉ mục nằm ngoài giới hạn: operator[]hủy bỏ với thông báo lỗi, at()ném một ngoại lệ.
James Kanze

2
Xin lỗi, đã bỏ lỡ thuộc tính "ở chế độ gỡ lỗi" của bạn. Tuy nhiên, tôi sẽ không đo lường mã về chất lượng của nó trong chế độ gỡ lỗi. Trong chế độ phát hành, kiểm tra chỉ được yêu cầu bởi at().
Sebastian Mach

1
@phresnel Hầu hết mã tôi đã gửi đều ở chế độ "gỡ lỗi". Bạn chỉ tắt kiểm tra khi các vấn đề về hiệu suất thực sự yêu cầu. (Microsoft trước năm 2010 có một chút vấn đề ở đây, vì std::stringkhông phải lúc nào cũng hoạt động nếu các tùy chọn kiểm tra không tương ứng với các tùy chọn của thời gian chạy: -MDvà tốt hơn là bạn nên tắt kiểm tra -MDd, và tốt hơn là bạn nên tiếp tục.)
James Kanze

2
Tôi thích trại hơn nói rằng "mã được xử phạt (đảm bảo) theo tiêu chuẩn"; tất nhiên, bạn có thể tự do cung cấp ở chế độ gỡ lỗi, nhưng khi thực hiện phát triển nền tảng chéo (bao gồm, nhưng không riêng, trường hợp của cùng một hệ điều hành, nhưng các phiên bản trình biên dịch khác nhau), dựa vào tiêu chuẩn là cách tốt nhất cho các bản phát hành và chế độ gỡ lỗi được coi là một công cụ cho các lập trình viên để có được điều mà hầu hết là chính xác và mạnh mẽ :)
Sebastian Mach

1

Toàn bộ điểm của việc sử dụng ngoại lệ là mã xử lý lỗi của bạn có thể xa hơn.

Trong trường hợp cụ thể này, đầu vào của người dùng thực sự là một ví dụ điển hình. Hãy tưởng tượng bạn muốn phân tích ngữ nghĩa một cấu trúc dữ liệu XML sử dụng các chỉ mục để tham chiếu đến một số loại tài nguyên mà bạn lưu trữ nội bộ trong a std::vector. Bây giờ cây XML là một cây, vì vậy bạn có thể muốn sử dụng đệ quy để phân tích nó. Sâu xa hơn, trong đệ quy, có thể có vi phạm quyền truy cập bởi người viết tệp XML. Trong trường hợp đó, bạn thường muốn loại bỏ tất cả các cấp độ đệ quy và chỉ từ chối toàn bộ tệp (hoặc bất kỳ loại cấu trúc "thô hơn" nào). Đây là nơi có ích. Bạn chỉ có thể viết mã phân tích như thể tệp hợp lệ. Mã thư viện sẽ đảm nhận việc phát hiện lỗi và bạn chỉ có thể bắt lỗi ở cấp độ thô.

Ngoài ra, các vùng chứa khác, chẳng hạn như std::map, cũng có std::map::atngữ nghĩa hơi khác với std::map::operator[]: at có thể được sử dụng trên bản đồ const, trong khi operator[]không thể. Bây giờ, nếu bạn muốn viết mã bất khả tri vùng chứa, như một thứ gì đó có thể giải quyếtconst std::vector<T>& hoặc const std::map<std::size_t, T>&, ContainerType::atsẽ là vũ khí bạn lựa chọn.

Tuy nhiên, tất cả các trường hợp này thường xuất hiện khi xử lý một số loại đầu vào dữ liệu chưa được xác thực. Nếu bạn chắc chắn về phạm vi hợp lệ của mình, như thường lệ, bạn thường có thể sử dụng operator[], nhưng tốt hơn, các trình vòng lặp với begin()end().


1

Theo này bài viết, thực hiện sang một bên, nó không thực hiện bất kỳ sự khác biệt để sử dụng athoặc operator[], chỉ khi truy cập được đảm bảo là trong kích thước của vector. Ngược lại, nếu truy cập chỉ dựa trên dung lượng của vector thì sẽ an toàn hơn khi sử dụng at.


1
ngoài kia có rồng. điều gì xảy ra nếu chúng ta nhấp vào liên kết đó? (gợi ý: Tôi biết rồi, nhưng trên StackOverflow, chúng tôi thích các nhận xét không bị thối liên kết, tức là cung cấp một bản tóm tắt ngắn về những gì bạn muốn nói)
Sebastian Mach

Cảm ơn vì tiền hỗ trợ. Nó đã được sửa ngay bây giờ.
ahj

0

Lưu ý: Có vẻ như một số người mới đang phản đối câu trả lời này mà không cần lịch sự cho biết điều gì sai. Câu trả lời dưới đây là chính xác và có thể được xác minh tại đây .

Thực sự chỉ có một sự khác biệt: atkiểm tra giới hạn trong khi operator[]thì không. Điều này áp dụng cho các bản dựng gỡ lỗi cũng như các bản dựng phát hành và điều này được quy định rất rõ bởi các tiêu chuẩn. Nó đơn giản mà.

Điều này làm cho atmột phương pháp chậm hơn nhưng cũng thực sự không nên sử dụng at. Bạn phải nhìn vào số tuyệt đối, không phải số tương đối. Tôi có thể đặt cược một cách an toàn rằng hầu hết mã của bạn đang thực hiện các hoạt động tốn kém hơn at. Cá nhân tôi cố gắng sử dụng atvì tôi không muốn một lỗi khó chịu tạo ra hành vi không xác định và lẻn vào sản xuất.


1
Các ngoại lệ trong C ++ được hiểu là một cơ chế xử lý lỗi, không phải là một công cụ để gỡ lỗi. Herb Sutter giải thích lý do tại sao ném std::out_of_rangehoặc bất kỳ hình thức nào std::logic_error, trên thực tế, là một lỗi logic trong và của chính nó ở đây .
Big Temp

@BigTemp - Tôi không chắc nhận xét của bạn liên quan đến câu hỏi và câu trả lời này như thế nào. Vâng, ngoại lệ là chủ đề được tranh luận nhiều nhưng câu hỏi ở đây là sự khác biệt giữa at[]và câu trả lời của tôi chỉ đơn giản là sự khác biệt. Cá nhân tôi sử dụng phương pháp "an toàn" khi hiệu suất không phải là một vấn đề. Như Knuth nói, đừng tối ưu hóa quá sớm. Ngoài ra, việc phát hiện lỗi sớm hơn trong quá trình sản xuất sẽ rất tốt bất kể sự khác biệt về triết lý.
Shital Shah
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.