2015-09-21 16 views
21

Trong Herb Sutter When Is a Container Not a Container?, ông cho thấy một ví dụ về việc một con trỏ vào một container:Đối với một véc tơ, tại sao lại thích một trình lặp trên một con trỏ?

// Example 1: Is this code valid? safe? good? 
    // 
    vector<char> v; 

    // ... 

    char* p = &v[0]; 

    // ... do something with *p ... 

Rồi sau nó lên với một "cải thiện":

// Example 1(b): An improvement 
    //    (when it's possible) 
    // 
    vector<char> v; 

    // ... 

    vector<char>::iterator i = v.begin(); 

    // ... do something with *i ... 

Nhưng không thực sự cung cấp một đối số thuyết phục:

Nói chung, không phải là hướng dẫn xấu để sử dụng trình biến đổi thay vì của con trỏ khi bạn muốn trỏ t một vật nằm bên trong một hộp chứa . Sau khi tất cả, các trình vòng lặp không hợp lệ tại hầu hết các thời điểm giống nhau và cùng một cách như con trỏ, và một lý do mà các vòng lặp tồn tại là cung cấp cách "trỏ" tại một đối tượng chứa. Vì vậy, nếu bạn có lựa chọn, thích sử dụng trình vòng lặp vào vùng chứa.

Thật không may, bạn không thể luôn có được hiệu ứng tương tự với trình vòng lặp mà bạn có thể với con trỏ vào vùng chứa. Có hai chính nhược điểm tiềm năng với phương pháp lặp, và khi một trong hai chúng tôi áp dụng phải tiếp tục sử dụng con trỏ:

  1. Bạn có thể không phải lúc nào thuận tiện sử dụng một iterator nơi bạn có thể sử dụng một con trỏ. (Xem ví dụ bên dưới.)

  2. Sử dụng trình vòng lặp có thể phát sinh thêm không gian và chi phí hiệu năng, trong trường hợp trình lặp là đối tượng chứ không chỉ là con trỏ hói .

Trong trường hợp của một véc tơ, biến lặp chỉ là một RandomAccessIterator. Đối với tất cả các ý định và mục đích này là một wrapper mỏng trên một con trỏ. Một thực hiện thậm chí thừa nhận điều này:

// This iterator adapter is 'normal' in the sense that it does not 
    // change the semantics of any of the operators of its iterator 
    // parameter. Its primary purpose is to convert an iterator that is 
    // not a class, e.g. a pointer, into an iterator that is a class. 
    // The _Container parameter exists solely so that different containers 
    // using this template can instantiate different types, even if the 
    // _Iterator parameter is the same. 

Bên cạnh đó, việc thực hiện lưu trữ một giá trị thành viên của loại _Iterator, đó là pointer hoặc T*. Nói cách khác, chỉ cần một con trỏ. Hơn nữa, difference_type cho loại như vậy là std::ptrdiff_t và các hoạt động được xác định chỉ là trình bao bọc mỏng (tức là, operator++++_pointer, operator**_pointer) v.v.

Theo đối số của Sutter, lớp trình lặp này không mang lại lợi ích nào so với con trỏ, chỉ có nhược điểm. Tôi có đúng không?

+1

Không có 'giới hạn', điều này có ý nghĩa rất ít khi nói hiệu suất trên một trong hai. – user3528438

+2

Có vẻ như vấn đề là tự lừa dối bạn bằng cách gọi các trình bao bọc giấy mỏng của các trình vòng lặp con trỏ. Nếu bạn gọi một con trỏ là một con trỏ và để lại thuật ngữ vòng lặp cho một thứ mà chỉ bao giờ trỏ đến một trong các thành viên của một thùng chứa, vv thì nó trở thành một sự lựa chọn thực sự giữa một trình lặp với các biện pháp bảo vệ và một con trỏ không có. – Paddy3118

Trả lời

25

Đối với vectơ, trong mã không phải là chung chung, bạn chủ yếu là chính xác.

Lợi ích là bạn có thể chuyển RandomAccessIterator cho toàn bộ các thuật toán cho dù container có lặp đi lặp lại hay không, cho dù vùng chứa đó có lưu trữ liền kề hay không. Đó là một trừu tượng.

(Sự trừu tượng này, trong số những thứ khác, cho phép thực hiện hoán đổi việc triển khai con trỏ cơ bản cho điều gì đó gợi cảm hơn, giống như trình vòng lặp kiểm tra phạm vi để gỡ lỗi sử dụng.)

Thường được coi là thói quen tốt để sử dụng trình vòng lặp trừ khi bạn thực sự không thể. Sau khi tất cả, thói quen giống nhất quán, và nhất quán dẫn đến khả năng bảo trì.

Iterator cũng tự ghi lại tài liệu theo cách mà con trỏ không có. Điểm int* là gì? Không ý kiến. Điểm std::vector<int>::iterator là gì? Aha & hellip;

Cuối cùng, họ cung cấp một biện pháp an toàn một loại — dù lặp như vậy chỉ có thể wrappers mỏng xung quanh con trỏ, họ không cần được gợi ý: nếu một iterator là một loại riêng biệt chứ không phải là một loại bí danh, sau đó bạn thắng không được vô tình chuyển iterator của bạn vào những nơi bạn không muốn nó đi, hoặc thiết lập nó để "NULL" vô tình.

Tôi đồng ý rằng đối số của Sutter là thuyết phục như hầu hết các đối số khác của ông, tức là không phải là rất nhiều.

10

Một lý do thực tế để sử dụng trình vòng lặp trên con trỏ là chúng có thể được triển khai dưới dạng checked iterators trong quá trình xây dựng gỡ lỗi và giúp bạn nắm bắt một số vấn đề khó chịu sớm. Tức là:

vector<int>::iterator it; // uninitialized iterator 
it++; 

hoặc

for (it = vec1.begin(); it != vec2.end(); ++it) // different containers 
+1

Có một chút lúng túng khi một trình lặp vector có một hàm tạo mặc định. –

+0

@Viktor Về mặt kỹ thuật, nó chưa được khởi tạo.Một RandomAccessIterator là một ForwardIterator, được yêu cầu là DefaultConstructible (và kết quả là, giá trị khởi tạo được). Việc thực hiện tôi tham chiếu không chỉ này: '__normal_iterator(): _M_current (_Iterator()) {}' Tất nhiên, điều này không làm cho 'nó ++;' bất kỳ được xác định rõ hơn, nhưng vẫn còn. – user5360395

+0

@ user5360395 Ah, suy nghĩ về nó tôi đoán một số thuật toán đòi hỏi phải lặp xây dựng mặc định hoặc một cái gì đó. –

10

Bạn có thể không phải lúc nào thuận tiện sử dụng một iterator nơi bạn có thể sử dụng một con trỏ

Đó là không một trong những nhược điểm. Đôi khi nó chỉ là quá "thuận tiện" để có được con trỏ thông qua đến những nơi mà bạn thực sự không muốn họ đi. Có một loại riêng biệt giúp xác thực các thông số.

Một số triển khai sớm được sử dụng T* cho vector :: iterator, nhưng nó gây ra nhiều vấn đề khác nhau, như mọi người vô tình truyền con trỏ không liên quan đến hàm thành viên vectơ. Hoặc gán NULL cho trình lặp.

Sử dụng trình vòng lặp có thể chiếm thêm không gian và chi phí hiệu suất, trong trường hợp trình lặp là đối tượng chứ không chỉ là con trỏ hói.

Điều này được viết vào năm 1999, khi chúng tôi cũng tin rằng mã trong <algorithm> nên được tối ưu hóa cho các loại vùng chứa khác nhau. Không lâu sau đó, mọi người đều ngạc nhiên khi thấy rằng các trình biên dịch đã tìm ra điều đó. Các thuật toán chung sử dụng các trình vòng lặp làm việc tốt!

Đối với một std :: vector, hoàn toàn không có khoảng trống thời gian để sử dụng trình lặp thay vì con trỏ. Bạn phát hiện ra rằng lớp iterator chỉ là một wrapper mỏng trên một con trỏ. Trình biên dịch cũng sẽ thấy rằng, và tạo ra mã tương đương.

+1

_ "Có một kiểu riêng biệt giúp xác nhận các tham số" _ Chúng không chỉ là loại bí danh ngày nay? Tốt. –

+1

Ah, vâng. Tôi có linh cảm rằng nó liên quan đến an toàn kiểu. Cảm ơn bạn đã chỉ ra những gì tôi gặp khó khăn khi khái niệm hóa. – user5360395

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