Chênh lệch insert & push_back của vector C ++


79

Tôi muốn biết (các) sự khác biệt giữa vector's push_backvà các insertchức năng là gì.

Có khác biệt về cấu trúc không?

Có sự khác biệt về hiệu suất thực sự lớn không?


4
insertcó thể làm điều đó ở bất cứ đâu và bao gồm những thứ khác như hỗ trợ phạm vi. push_backthuận tiện hơn cho việc thêm vào cuối.
chris

Câu trả lời:


93

Sự khác biệt lớn nhất là chức năng của chúng. push_backluôn đặt một phần tử mới ở cuối vectorinsertcho phép bạn chọn vị trí của phần tử mới. Điều này ảnh hưởng đến hiệu suất. vectorcác phần tử chỉ được di chuyển trong bộ nhớ khi cần thiết để tăng độ dài của nó vì quá ít bộ nhớ được cấp cho nó. Mặt khác insertbuộc phải di chuyển tất cả các phần tử sau vị trí đã chọn của một phần tử mới. Bạn chỉ cần tạo một chỗ cho nó. Đây là lý do tại sao insertthường có thể kém hiệu quả hơn push_back.


9
Mặt khác, việc chèn ở cuối phải hiệu quả như push_back.
Matthieu M.

18
Chà, gần như là hiệu quả - mã cần xác định rằng cái insertthực sự ở tại end(), vì vậy sẽ có nhiều nhánh inserthơn ở bên trong push_back, trừ khi trình biên dịch có thể nhận ra rằng chúng thực sự không cần thiết.
Yakk - Adam Nevraumont

5
@TomerW Mọi người chọn C hoặc C ++ để có thể yêu cầu mọi chu kỳ máy đơn lẻ mà họ có thể, để tăng hiệu suất, đến mức đôi khi viết trực tiếp trong ASM ... mọi "nếu" đều được tính, đây không phải là java ..
Cesar

1
@Cesar Không phải tại sao bạn chọn ngôn ngữ của mình ... cpp có thể nhanh hơn c, và ngược lại ... quái, trong một số trường hợp, ngay cả java cũng có thể nhanh hơn c để tối ưu hóa Runtime.
Tomer W

@TomerW ho, vậy tại sao? làm ơn khai sáng cho tôi.
Cesar

32

Các chức năng có các mục đích khác nhau. vector::insertcho phép bạn chèn một đối tượng tại một vị trí xác định trong vector, trong khi vector::push_backsẽ chỉ dán đối tượng vào cuối. Xem ví dụ sau:

using namespace std;
vector<int> v = {1, 3, 4};
v.insert(next(begin(v)), 2);
v.push_back(5);
// v now contains {1, 2, 3, 4, 5}

Bạn có thể sử dụng insertđể thực hiện công việc tương tự như push_backvới v.insert(v.end(), value).


9

Bên cạnh thực tế, điều push_back(x)đó tương tự như insert(x, end())(có thể với hiệu suất tốt hơn một chút), có một số điều quan trọng cần biết về các chức năng này:

  1. push_backchỉ tồn tại trên BackInsertionSequencecác vùng chứa - vì vậy, ví dụ, nó không tồn tại trên set. Nó không thể vì push_back()cấp cho bạn mà nó sẽ luôn thêm vào cuối.
  2. Một số thùng chứa cũng có thể đáp ứng FrontInsertionSequencevà họ có push_front. Điều này được hài lòng bởi deque, nhưng không phải bởi vector.
  3. insert(x, ITERATOR)từ InsertionSequence, là chung cho setvector. Bằng cách này, bạn có thể sử dụng sethoặc vectorlàm mục tiêu cho nhiều lần chèn. Tuy nhiên, setcó bổ sung insert(x), mà không thực tế điều tương tự (chèn đầu tiên này trong setphương tiện duy nhất để tăng tốc độ tìm kiếm nơi thích hợp bằng cách bắt đầu từ một iterator khác nhau - một tính năng không được sử dụng trong trường hợp này).

Lưu ý về trường hợp cuối cùng rằng nếu bạn định thêm các phần tử vào vòng lặp, thì hãy làm container.push_back(x)container.insert(x, container.end())sẽ thực hiện hiệu quả điều tương tự. Tuy nhiên, điều này sẽ không đúng nếu bạn nhận được điều này container.end()đầu tiên và sau đó sử dụng nó trong toàn bộ vòng lặp.

Ví dụ: bạn có thể mạo hiểm với mã sau:

auto pe = v.end();
for (auto& s: a)
    v.insert(pe, v);

Điều này sẽ sao chép hiệu quả toàn bộ athành vvectơ, theo thứ tự ngược lại và chỉ khi bạn đủ may mắn để không bị phân bổ lại vectơ cho phần mở rộng (bạn có thể ngăn điều này bằng cách gọi reserve()trước); nếu bạn không may mắn như vậy, bạn sẽ nhận được cái gọi là UndefinedBehavior (tm). Về mặt lý thuyết, điều này không được phép vì các trình vòng lặp của vectơ được coi là vô hiệu mỗi khi một phần tử mới được thêm vào.

Nếu bạn làm theo cách này:

copy(a.begin(), a.end(), back_inserter(v);

nó sẽ sao chép aở cuối vtheo thứ tự ban đầu và điều này không có nguy cơ làm mất hiệu lực trình lặp.

[EDIT] Trước đây tôi đã thực hiện mã này theo cách này và đó là một sai lầm vì inserterthực sự duy trì tính hợp lệ và tiến bộ của trình lặp:

copy(a.begin(), a.end(), inserter(v, v.end());

Vì vậy, mã này cũng sẽ thêm tất cả các phần tử theo thứ tự ban đầu mà không có bất kỳ rủi ro nào.


Nhận xét của bạn về std :: insertter là "rủi ro" đối với các vùng chứa dạng vector là không chính xác. Trên thực tế, nó được thiết kế để loại bỏ những rủi ro này. Không có thứ tự ngược lại, không có hành vi không xác định. Xem ví dụ: flucpp.com/2017/10/06/stl-inserter-iterators-work để biết bảng phân tích.
downhillFromHere

Bạn đúng. Tôi chỉ muốn làm cho mã ngắn gọn thay vì mở rộng nó thành một vòng lặp thủ công mở rộng, vì vậy điều này thực sự không đúng vì inserterduy trì sự tiến bộ và hiệu lực của trình lặp. Tôi sẽ phải chỉnh sửa bài đăng :)
Ethouris
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.