đẩy_back vs emplace_back


761

Tôi hơi bối rối về sự khác biệt giữa push_backemplace_back.

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

Vì có push_backquá tải khi tham chiếu giá trị, tôi không hiểu mục đích của việc emplace_backtrở thành là gì?



16
Lưu ý rằng (như Thomas nói bên dưới), mã trong câu hỏi là từ mô phỏng C ++ 0x của MSVS , chứ không phải C ++ 0x thực sự là gì.
me22

5
Một bài viết tốt hơn để đọc sẽ là: open-std.org/jtc1/sc22/wg21/docs/ con 2007 / n2345.pdf . N2642 chủ yếu là từ ngữ cho Tiêu chuẩn; N2345 là bài viết giải thích và thúc đẩy ý tưởng.
Alan

Lưu ý rằng ngay cả trong MSVC10 cũng có một template <class _Valty> void emplace_back(_Valty&& _Val)phiên bản lấy tham chiếu phổ quát cung cấp chuyển tiếp hoàn hảo đến các hàm tạo explicitđối số duy nhất.
joki

Liên quan: Có trường hợp nào push_backthích hợp hơn emplace_back? Trường hợp duy nhất tôi có thể nghĩ là nếu một lớp nào đó có thể sao chép ( T&operator=(constT&)) nhưng không thể xây dựng ( T(constT&)), nhưng tôi không thể nghĩ tại sao người ta lại muốn điều đó.
Bến

Câu trả lời:


568

Ngoài những gì du khách nói:

Chức năng void emplace_back(Type&& _Val)được cung cấp bởi MSCV10 là không phù hợp và dự phòng, vì như bạn đã lưu ý, nó hoàn toàn tương đương với push_back(Type&& _Val).

Nhưng dạng C ++ 0x emplace_backthực sự rất hữu ích : void emplace_back(Args&&...);

Thay vì lấy một value_type danh sách các đối số có tính đột biến, điều đó có nghĩa là bây giờ bạn có thể chuyển tiếp hoàn hảo các đối số và xây dựng trực tiếp một đối tượng vào một thùng chứa mà không cần tạm thời.

Điều đó hữu ích bởi vì cho dù RVO có thông minh đến mức nào và di chuyển ngữ nghĩa mang đến bàn thì vẫn có những trường hợp phức tạp trong đó một Push_back có khả năng tạo ra các bản sao không cần thiết (hoặc di chuyển). Ví dụ, với insert()chức năng truyền thống của a std::map, bạn phải tạo tạm thời, sau đó sẽ được sao chép vào a std::pair<Key, Value>, sau đó sẽ được sao chép vào bản đồ:

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

// cross your finger so that the optimizer is really good
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// should be easier for the optimizer
m.emplace(4, anInt, aDouble, aString);

Vậy tại sao họ không triển khai đúng phiên bản emplace_back trong MSVC? Trên thực tế, nó đã làm tôi thất vọng một thời gian trước đây, vì vậy tôi đã hỏi cùng một câu hỏi trên blog Visual C ++ . Dưới đây là câu trả lời từ Stephan T Lavavej, người duy trì chính thức triển khai thư viện chuẩn Visual C ++ tại Microsoft.

H: Hiện tại các chức năng của beta 2 chỉ là một loại giữ chỗ nào đó phải không?

Trả lời: Như bạn có thể biết, các mẫu từ khóa không được triển khai trong VC10. Chúng tôi mô phỏng chúng với máy móc tiền xử lý cho những thứ như make_shared<T>(), tuple và những thứ mới trong <functional>. Máy móc tiền xử lý này tương đối khó sử dụng và bảo trì. Ngoài ra, nó ảnh hưởng đáng kể đến tốc độ biên dịch, vì chúng ta phải liên tục bao gồm các tiêu đề phụ. Do sự kết hợp giữa các hạn chế về thời gian và các mối quan tâm về tốc độ biên dịch, chúng tôi đã không mô phỏng các mẫu biến đổi trong các hàm của chúng tôi.

Khi các mẫu matrixdic được triển khai trong trình biên dịch, bạn có thể mong đợi rằng chúng ta sẽ tận dụng chúng trong các thư viện, bao gồm cả các hàm emplace của chúng ta. Chúng tôi rất coi trọng sự phù hợp, nhưng thật không may, chúng tôi không thể làm tất cả mọi thứ cùng một lúc.

Đó là một quyết định dễ hiểu. Tất cả những người đã thử chỉ một lần để mô phỏng khuôn mẫu dao động với các thủ thuật khủng khiếp tiền xử lý đều biết công cụ này kinh tởm đến mức nào.


101
Điều đó làm rõ rằng đó là vấn đề MSVS10, không phải vấn đề C ++ là phần quan trọng nhất ở đây. Cảm ơn.
me22

11
Tôi tin rằng dòng mã C ++ cuối cùng của bạn sẽ không hoạt động. pair<const int,Complicated>không có hàm tạo lấy một int, int khác, double và làm tham số thứ 4 một chuỗi. Tuy nhiên, bạn có thể trực tiếp xây dựng đối tượng cặp này bằng cách sử dụng hàm tạo piecewise của nó. Cú pháp sẽ khác nhau, tất nhiên:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze

3
Các mẫu matrixdic hạnh phúc sẽ có trong VS2013, hiện đang ở chế độ xem trước.
Daniel Earwicker

11
câu trả lời này có nên được cập nhật để phản ánh những phát triển mới trong vs2013 không?
vẫy gọi

6
Nếu bạn đang sử dụng Visual Studio 2013 hoặc sau nay , bạn nên có sự hỗ trợ cho các "thật" emplace_backquá lâu như nó đã được thực hiện vào Visual C ++ khi mẫu variadic đã được thêm vào: msdn.microsoft.com/en-us/library/hh567368. aspx
kayleeFrye_onDeck

200

emplace_backkhông nên lấy một đối số của kiểu vector::value_type, mà thay vào đó là các đối số có biến đổi được chuyển tiếp đến hàm tạo của mục được nối.

template <class... Args> void emplace_back(Args&&... args); 

Có thể chuyển một value_typecái sẽ được chuyển tiếp đến hàm tạo sao chép.

Bởi vì nó chuyển tiếp các đối số, điều này có nghĩa là nếu bạn không có giá trị, điều này vẫn có nghĩa là container sẽ lưu trữ một bản sao "sao chép", không phải là một bản sao được di chuyển.

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies

Nhưng ở trên nên giống hệt với những gì push_backkhông. Nó có lẽ là khá có nghĩa cho các trường hợp sử dụng như:

 std::vector<std::pair<std::string, std::string> > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // should end up invoking this constructor:
 //template<class U, class V> pair(U&& x, V&& y);
 //without making any copies of the strings

2
@David: nhưng sau đó bạn có di chuyển strong phạm vi, điều đó có nguy hiểm không?
Matthieu M.

2
Sẽ không nguy hiểm nếu bạn không có kế hoạch sử dụng s nữa vì giá trị của nó. Di chuyển không làm cho s không hợp lệ, di chuyển sẽ chỉ ăn cắp phân bổ bộ nhớ trong đã được thực hiện trong s và để nó ở trạng thái mặc định (không phân bổ) mà khi bị hủy sẽ ổn như thể bạn vừa gõ std :: string str;
David

4
@David: Tôi không chắc chắn rằng một đối tượng chuyển từ được yêu cầu phải hợp lệ cho bất kỳ việc sử dụng nào ngoại trừ việc phá hủy sau đó.
Ben Voigt

46
vec.emplace_back("Hello")sẽ hoạt động, vì const char*đối số sẽ được chuyển tiếp đến hàm stringtạo. Đây là toàn bộ quan điểm của emplace_back.
Alexandre C.

8
@BenVoigt: Một đối tượng chuyển từ được yêu cầu phải ở trạng thái hợp lệ (nhưng không xác định). Tuy nhiên, điều này không nhất thiết có nghĩa là bạn có thể thực hiện bất kỳ thao tác nào trên đó. Hãy xem xét std::vector. Một khoảng trống std::vectorlà một trạng thái hợp lệ, nhưng bạn không thể gọi front()nó. Điều này có nghĩa là bất kỳ hàm nào không có điều kiện tiên quyết vẫn có thể được gọi (và hàm hủy không bao giờ có thể có điều kiện tiên quyết).
David Stone

96

Tối ưu hóa cho emplace_back có thể được chứng minh trong ví dụ tiếp theo.

Đối với nhà emplace_backxây dựng A (int x_arg)sẽ được gọi. Và cho push_back A (int x_arg)được gọi đầu tiên và move A (A &&rhs)được gọi sau đó.

Tất nhiên, hàm tạo phải được đánh dấu là explicit, nhưng ví dụ hiện tại là tốt để loại bỏ nhân chứng.

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

đầu ra:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)

21
1 cho ví dụ mã đó chứng tỏ những gì thực sự xảy ra khi gọi emplace_backvs push_back.
Shawn

Tôi đến đây sau khi nhận thấy rằng tôi có mã đang gọi v.emplace_back(x);trong đó x rõ ràng có thể di chuyển được nhưng có thể sao chép rõ ràng có thể xây dựng được. Thực tế emplace_backlà "ngầm" rõ ràng khiến tôi nghĩ rằng chức năng đi đến của tôi để nối thêm có lẽ nên được push_back. Suy nghĩ?
Ben

Nếu bạn gọi a.emplace_backlần thứ hai, hàm tạo di chuyển sẽ được gọi!
X Æ A-12


8

emplace_back việc thực hiện phù hợp sẽ chuyển tiếp các đối số đến vector<Object>::value_type tạo khi thêm vào vectơ. Tôi nhớ rằng Visual Studio không hỗ trợ các mẫu matrixdic, nhưng với các mẫu matrixdic sẽ được hỗ trợ trong Visual Studio 2013 RC, vì vậy tôi đoán một chữ ký phù hợp sẽ được thêm vào.

Với emplace_back, nếu bạn chuyển tiếp các đối số trực tiếp đến hàm vector<Object>::value_typetạo, bạn không cần một loại có thể di chuyển hoặc sao chép được cho emplace_backchức năng, nói đúng. Trong vector<NonCopyableNonMovableObject>trường hợp, điều này không hữu ích, vìvector<Object>::value_type cần một loại có thể sao chép hoặc di chuyển để phát triển.

Nhưng lưu ý rằng điều này có thể hữu ích vì std::map<Key, NonCopyableNonMovableObject>, khi bạn phân bổ một mục trong bản đồ, nó không cần phải di chuyển hoặc sao chép nữa, không giống như vector, có nghĩa là bạn có thể sử dụng std::maphiệu quả với loại được ánh xạ không thể sao chép cũng không thể sao chép di chuyển.


8

Thêm một trường hợp trong danh sách:

// constructs the elements in place.                                                
emplace_back("element");


//It will create new object and then copy(or move) its value of arguments.
push_back(explicitDataType{"element"});

1

Trường hợp sử dụng cụ thể cho emplace_back: Nếu bạn cần tạo một đối tượng tạm thời sau đó sẽ được đẩy vào một thùng chứa, hãy sử dụng emplace_backthay vìpush_back . Nó sẽ tạo đối tượng tại chỗ trong container.

Ghi chú:

  1. push_backtrong trường hợp trên sẽ tạo một đối tượng tạm thời và di chuyển nó vào trong thùng chứa. Tuy nhiên, xây dựng tại chỗ được sử dụng emplace_backsẽ hiệu quả hơn so với xây dựng và sau đó di chuyển đối tượng (thường liên quan đến một số sao chép).
  2. Nói chung, bạn có thể sử dụng emplace_backthay vì push_backtrong tất cả các trường hợp mà không có vấn đề gì nhiều. (Xem trường hợp ngoại lệ )
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.