Trong trường hợp nào tôi sử dụng một container STL cụ thể?


184

Tôi đã đọc về các thùng chứa STL trong cuốn sách của mình về C ++, cụ thể là phần về STL và các thùng chứa của nó. Bây giờ tôi đã hiểu mỗi người trong số họ có các thuộc tính cụ thể của riêng họ và tôi gần như ghi nhớ tất cả chúng ... Nhưng điều tôi chưa nắm bắt được là kịch bản nào trong số chúng được sử dụng.

Giải thích là gì? Mã ví dụ được ưa thích hơn nhiều.


bạn có nghĩa là bản đồ, vectot, thiết lập vv?
Thomas Tempelmann

Ngay cả khi nhìn vào sơ đồ này, tôi không thể nói cái gì sẽ là thứ tốt nhất để sử dụng trong stackoverflow
mình.com /questions / 9329011 / giả

2
@sbi: Xóa thẻ Faq C ++ khỏi thẻ này và thêm nó vào gần đây và bao gồm C ++ 11 Làm thế nào tôi có thể chọn một thư viện thư viện tiêu chuẩn trong C ++ 11 một cách hiệu quả?
Alok Lưu

Câu trả lời:


336

Bảng cheat này cung cấp một bản tóm tắt khá tốt về các container khác nhau.

Xem sơ đồ ở phía dưới dưới dạng hướng dẫn sử dụng trong các tình huống sử dụng khác nhau:

http://linuxsoftware.co.nz/containerchoice.png

Được tạo bởi David MooreCC BY-SA 3.0 được cấp phép


14
Lưu đồ này là vàng, tôi ước tôi có một cái gì đó như thế trong c #
Bruno

2
Liên kết cập nhật: C ++ Container Cheat Sheet .
Cửa Bill

3
Điểm bắt đầu phải là vectorsau đó trống rỗng. stackoverflow.com/questions/10699265/
Eonil

5
Bây giờ bạn có unordered_mapunordered_set(và nhiều biến thể của chúng) không có trong biểu đồ luồng nhưng là lựa chọn tốt khi bạn không quan tâm đến trật tự mà cần tìm các yếu tố theo khóa. Tra cứu của họ thường là O (1) thay vì O (log n).
Aidiakapi

2
@ Shuttle87 không chỉ kích thước đó sẽ không bao giờ thay đổi, mà quan trọng hơn là kích thước đó được xác định tại thời điểm biên dịch và sẽ không bao giờ thay đổi.
YoungJohn 24/07/2015

188

Đây là một sơ đồ lấy cảm hứng từ phiên bản của David Moore (xem ở trên) mà tôi đã tạo, được cập nhật (chủ yếu) với tiêu chuẩn mới (C ++ 11). Đây chỉ là vấn đề cá nhân của tôi, không thể chối cãi, nhưng tôi cho rằng nó có thể có giá trị cho cuộc thảo luận này:

nhập mô tả hình ảnh ở đây


4
Bạn có thể làm cho bản gốc có sẵn? Đó là một biểu đồ tuyệt vời. Có thể dính vào một blog hoặc GitHub?
kevinarpe

1
Đây là một biểu đồ tuyệt vời. Mặc dù ai đó có thể giải thích cho tôi ý nghĩa của 'các vị trí cố định' là gì?
IDDQD

3
@STALKER Vị trí liên tục có nghĩa là nếu bạn có một con trỏ hoặc iterator cho một phần tử trong vùng chứa, thì con trỏ hoặc iterator đó sẽ vẫn hợp lệ (và trỏ đến cùng một phần tử) bất kể bạn thêm hoặc xóa gì khỏi container (miễn là nó không phải là yếu tố trong câu hỏi).
Mikael Persson

1
Đây thực sự là một biểu đồ tuyệt vời, tuy nhiên tôi nghĩ vector (sorted)là một chút không phù hợp với phần còn lại. Nó không phải là một loại container khác nhau, chỉ giống nhau std::vectornhưng được sắp xếp. Thậm chí quan trọng hơn, tôi không thấy lý do tại sao người ta không thể sử dụng một std::setlần lặp theo thứ tự nếu đó là hành vi tiêu chuẩn của việc lặp qua máng một bộ. Chắc chắn, nếu câu trả lời là nói về việc truy cập có trật tự các giá trị của máng chứa [], thì ok bạn chỉ có thể làm điều đó với một dấu chấm std::vector. Nhưng trong cả hai trường hợp, quyết định nên được đưa ra ngay sau câu hỏi "cần đặt hàng"
RA

1
@ user2019840 Tôi muốn giới hạn biểu đồ cho các thùng chứa tiêu chuẩn. Những gì sẽ xuất hiện thay cho "vectơ được sắp xếp" là "Flat_set" (từ Boost.Container ) hoặc tương đương (mọi thư viện chính hoặc cơ sở mã đều có tương đương phẳng_set, AFAIK). Nhưng đây là những tiêu chuẩn không phù hợp và khá thiếu sót từ STL. Và lý do tại sao bạn không muốn lặp lại thông qua std :: set hoặc std :: map (ít nhất là không thường xuyên) là vì nó rất không hiệu quả để làm như vậy .
Mikael Persson

42

Câu trả lời đơn giản: sử dụng std::vectorcho mọi thứ trừ khi bạn có lý do thực sự để làm khác.

Khi bạn tìm thấy một trường hợp mà bạn đang nghĩ, "Gee, std::vectorkhông hoạt động tốt ở đây vì X", hãy dựa trên X.


1
Tuy nhiên .. hãy cẩn thận không xóa / chèn các mục khi lặp ... sử dụng const_iterator càng xa càng tốt để tránh điều này ..
vrdhn

11
Hmm ... Tôi nghĩ rằng mọi người đang sử dụng quá nhiều vector. Lý do là, trường hợp "không hoạt động" sẽ không dễ dàng xảy ra - vì vậy mọi người dính vào container thường được sử dụng nhất và sử dụng sai mục đích để lưu trữ danh sách, hàng đợi, ... Trong ý kiến ​​của tôi - phù hợp với sơ đồ - người ta nên chọn container dựa trên mục đích sử dụng thay vì áp dụng "một cái có vẻ phù hợp với tất cả".
Đen

13
@Black Point là, vector thường nhanh hơn ngay cả trên các hoạt động mà theo lý thuyết nên hoạt động chậm hơn.
Bartek Banachewicz

1
@Vardhan std::remove_ifhầu như luôn vượt trội so với phương pháp "xóa trong quá trình lặp".
dòng chảy

1
Một số điểm chuẩn sẽ thực sự giúp cuộc thảo luận này ít chủ quan hơn.
Felix D.

11

Nhìn vào STL hiệu quả của Scott Meyers. Thật tốt khi giải thích cách sử dụng STL.

Nếu bạn muốn lưu trữ một số lượng đối tượng xác định / không xác định và bạn sẽ không bao giờ xóa bất kỳ, thì một vectơ là những gì bạn muốn. Đây là sự thay thế mặc định cho mảng C và nó hoạt động như một mảng, nhưng không tràn. Bạn có thể đặt kích thước của nó trước cũng như với dự trữ ().

Nếu bạn muốn lưu trữ một số lượng đối tượng không xác định, nhưng bạn sẽ thêm chúng và xóa chúng, thì có lẽ bạn muốn có một danh sách ... bởi vì bạn có thể xóa một phần tử mà không di chuyển bất kỳ phần tử nào sau đây - không giống như vectơ. Tuy nhiên, nó chiếm nhiều bộ nhớ hơn một vectơ và bạn không thể truy cập tuần tự một phần tử.

Nếu bạn muốn lấy một loạt các phần tử và chỉ tìm các giá trị duy nhất của các phần tử đó, đọc tất cả chúng thành một bộ sẽ làm điều đó và nó cũng sẽ sắp xếp chúng cho bạn.

Nếu bạn có nhiều cặp khóa-giá trị và bạn muốn sắp xếp chúng theo khóa, thì bản đồ sẽ hữu ích ... nhưng nó sẽ chỉ giữ một giá trị cho mỗi khóa. Nếu bạn cần nhiều hơn một giá trị cho mỗi khóa, bạn có thể có một vectơ / danh sách làm giá trị của mình trong bản đồ hoặc sử dụng nhiều chế độ.

Nó không có trong STL, nhưng đó là trong bản cập nhật TR1 cho STL: nếu bạn có nhiều cặp giá trị khóa mà bạn sẽ tìm kiếm theo khóa và bạn không quan tâm đến thứ tự của chúng, bạn có thể muốn sử dụng hàm băm - đó là tr1 :: unordered_map. Tôi đã sử dụng nó với Visual C ++ 7.1, nơi nó được gọi là stdext :: hash_map. Nó có tra cứu O (1) thay vì tra cứu O (log n) cho bản đồ.


Tôi đã nghe một vài giai thoại bây giờ cho thấy rằng Microsoft hash_mapkhông phải là một triển khai rất tốt. Tôi hy vọng họ đã làm tốt hơn trên unordered_map.
Đánh dấu tiền chuộc

3
Danh sách - "bạn không thể truy cập tuần tự một yếu tố." - Tôi nghĩ bạn có nghĩa là bạn không thể truy cập ngẫu nhiên hoặc lập chỉ mục trực tiếp đến một yếu tố ....
Tony Delroy

^ Có, bởi vì truy cập tuần tự chính xác là những gì a listlàm. Lỗi chói khá đấy.
gạch dưới

7

Tôi đã thiết kế lại sơ đồ để có 3 thuộc tính:

  1. Tôi nghĩ rằng các container STL được chia thành 2 lớp chính. Các container cơ bản và những container tận dụng các container cơ bản để thực hiện chính sách.
  2. Đầu tiên, sơ đồ nên phân chia quá trình quyết định cho các tình huống chính mà chúng ta nên quyết định và sau đó giải thích chi tiết cho từng trường hợp.
  3. Một số container mở rộng có khả năng chọn container cơ bản khác nhau làm container bên trong của chúng. Lưu đồ nên xem xét các tình huống trong đó mỗi thùng chứa cơ bản có thể được sử dụng.

Lưu đồ: nhập mô tả hình ảnh ở đây

Thêm thông tin được cung cấp trong liên kết này .


5

Một điểm quan trọng chỉ giới thiệu vắn tắt cho đến nay, là nếu bạn yêu cầu bộ nhớ kề nhau (giống như một mảng C cho), sau đó bạn chỉ có thể sử dụng vector, arrayhoặc string.

Sử dụng arraynếu kích thước được biết tại thời gian biên dịch.

Sử dụng stringnếu bạn chỉ cần làm việc với các loại ký tự và cần một chuỗi, không chỉ là một thùng chứa mục đích chung.

Sử dụng vectortrong tất cả các trường hợp khác ( vectornên là lựa chọn mặc định của container trong hầu hết các trường hợp).

Với cả ba điều này, bạn có thể sử dụng data()hàm thành viên để lấy một con trỏ tới phần tử đầu tiên của vùng chứa.


3

Tất cả phụ thuộc vào những gì bạn muốn lưu trữ và những gì bạn muốn làm với container. Dưới đây là một số ví dụ (rất không đầy đủ) cho các lớp container mà tôi có xu hướng sử dụng nhiều nhất:

vector: Bố cục nhỏ gọn với ít hoặc không có chi phí bộ nhớ cho mỗi đối tượng chứa. Hiệu quả để lặp đi lặp lại. Nối, chèn và xóa có thể tốn kém, đặc biệt đối với các đối tượng phức tạp. Giá rẻ để tìm một đối tượng chứa theo chỉ mục, ví dụ myVector [10]. Sử dụng nơi bạn đã sử dụng một mảng trong C. Tốt nơi bạn có rất nhiều đối tượng đơn giản (ví dụ int). Đừng quên sử dụng reserve()trước khi thêm nhiều đối tượng vào thùng chứa.

list: Bộ nhớ nhỏ trên mỗi đối tượng chứa. Hiệu quả để lặp đi lặp lại. Nối, chèn và xóa là giá rẻ. Sử dụng nơi bạn đã sử dụng một danh sách được liên kết trong C.

set(và multiset): Chi phí bộ nhớ đáng kể cho mỗi đối tượng được chứa. Sử dụng nơi bạn cần tìm hiểu nhanh nếu thùng chứa đó chứa một đối tượng nhất định hoặc hợp nhất các container một cách hiệu quả.

map(và multimap): Chi phí bộ nhớ đáng kể cho mỗi đối tượng được chứa. Sử dụng nơi bạn muốn lưu trữ các cặp khóa-giá trị và tìm kiếm các giá trị theo khóa một cách nhanh chóng.

Biểu đồ dòng chảy trên bảng cheat được đề xuất bởi zdan cung cấp một hướng dẫn đầy đủ hơn.


"Chi phí bộ nhớ nhỏ cho mỗi đối tượng chứa" không đúng với danh sách. std :: list được triển khai dưới dạng danh sách liên kết đôi và do đó nó duy trì 2 con trỏ cho mỗi đối tượng được lưu trữ mà không được bỏ qua.
Hanna Khalil

Tôi vẫn sẽ tính hai con trỏ trên mỗi đối tượng được lưu trữ là "nhỏ".
Hồ sơ dự thầu

so với cái gì std :: Forward_list là một thùng chứa chủ yếu được đề xuất là có ít dữ liệu meta được lưu trữ trên mỗi đối tượng (chỉ có một con trỏ). Trong khi std :: vector giữ 0 dữ liệu meta cho mỗi đối tượng. Vì vậy, 2 con trỏ không thể thương lượng so với các container khác
Hanna Khalil

Tất cả phụ thuộc vào kích thước của các đối tượng của bạn. Tôi đã nói rằng vectơ có "Bố cục nhỏ gọn với ít hoặc không có chi phí bộ nhớ cho mỗi đối tượng chứa". Tôi vẫn sẽ nói rằng danh sách có một chi phí bộ nhớ nhỏ so với thiết lập và bản đồ, và chi phí bộ nhớ lớn hơn một chút so với vector. Tôi không thực sự chắc chắn về điểm mà bạn đang cố gắng thực hiện TBH!
Hồ sơ dự thầu

Tất cả các container dựa trên chế độ có xu hướng có chi phí đáng kể do phân bổ động, hiếm khi miễn phí. Tất nhiên trừ khi bạn đang sử dụng một cấp phát tùy chỉnh.
MikeMB

2

Một bài học tôi đã học được là: Cố gắng bọc nó trong một lớp học, vì việc thay đổi loại container một ngày đẹp trời có thể mang lại những bất ngờ lớn.

class CollectionOfFoo {
    Collection<Foo*> foos;
    .. delegate methods specifically 
}

Nó không tốn nhiều chi phí trước và tiết kiệm thời gian gỡ lỗi khi bạn muốn phá vỡ bất cứ khi nào ai đó thực hiện thao tác x trên cấu trúc này.

Đến để chọn cấu trúc dữ liệu hoàn hảo cho công việc:

Mỗi cấu trúc dữ liệu cung cấp một số thao tác, có thể thay đổi độ phức tạp thời gian khác nhau:

O (1), O (lg N), O (N), v.v.

Về cơ bản, bạn phải dự đoán tốt nhất, hoạt động nào sẽ được thực hiện nhiều nhất và sử dụng cấu trúc dữ liệu có hoạt động đó là O (1).

Đơn giản, không phải vậy (-:


5
Đây không phải là lý do tại sao chúng ta sử dụng các trình vòng lặp?
Bạch kim Azure

@PlatinumAzure Ngay cả các trình vòng lặp cũng phải là thành viên typedef .. Nếu bạn thay đổi loại vùng chứa, bạn cũng phải đi và thay đổi tất cả các định nghĩa của trình vòng lặp ... điều này đã được sửa trong c ++ 1x!
vrdhn

4
Đối với người tò mò, đây là bản sửa lỗi trong C ++ 11: auto myIterator = whateverCollection.begin(); // <-- immune to changes of container type
Đen

1
Sẽ typedef Collection<Foo*> CollectionOfFoo;là đủ?
Craig McQueen

5
Rất khó để bạn có thể thay đổi suy nghĩ của mình sau đó và chỉ cần ủy thác cho một container khác: Coi chừng ảo tưởng về mã độc lập với container
fredoverflow


1

Tôi đã trả lời câu hỏi này trong một câu hỏi khác được đánh dấu là bản sao của câu hỏi này. Nhưng tôi cảm thấy thật tuyệt khi tham khảo một số bài viết hay liên quan đến quyết định chọn một container tiêu chuẩn.

Như @David Thornley đã trả lời, std :: vector là con đường để đi nếu không có nhu cầu đặc biệt nào khác. Đây là lời khuyên được đưa ra bởi người tạo ra C ++, Bjarne Stroustrup trong một blog năm 2014.

Đây là liên kết cho bài viết https://isocpp.org/blog/2014/06/stroustrup-lists

và trích dẫn từ đó,

Và, vâng, đề nghị của tôi là sử dụng std :: vector theo mặc định.

Trong các bình luận, người dùng @NathanOliver cũng cung cấp một blog tốt khác, có số đo cụ thể hơn. https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html .

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.