chèn vs emplace vs toán tử [] trong bản đồ c ++


188

Tôi đang sử dụng bản đồ lần đầu tiên và tôi nhận ra rằng có nhiều cách để chèn một yếu tố. Bạn có thể sử dụng emplace(), operator[]hoặc insert(), cộng với các biến thể như sử dụng value_typehoặc make_pair. Mặc dù có rất nhiều thông tin về tất cả chúng và câu hỏi về các trường hợp cụ thể, tôi vẫn không thể hiểu được bức tranh lớn. Vì vậy, hai câu hỏi của tôi là:

  1. Lợi thế của mỗi người trong số họ so với những người khác là gì?

  2. Có cần thêm emplace vào tiêu chuẩn không? Có bất cứ điều gì không thể trước đây mà không có nó?


1
Ngữ nghĩa vị trí cho phép chuyển đổi rõ ràng và khởi tạo trực tiếp.
Kerrek SB

3
Bây giờ operator[]là dựa trên try_emplace. Nó có thể được đề cập insert_or_assignlà tốt.
FrankHB

@FrankHB nếu bạn (hoặc người khác) thêm câu trả lời cập nhật, tôi có thể thay đổi câu trả lời được chấp nhận.
Đức Capuano

Câu trả lời:


226

Trong trường hợp cụ thể của bản đồ, các tùy chọn cũ chỉ có hai: operator[]insert(các hương vị khác nhau insert). Vì vậy, tôi sẽ bắt đầu giải thích những điều đó.

Đây operator[]là một toán tử tìm hoặc thêm . Nó sẽ cố gắng tìm một phần tử có khóa đã cho bên trong bản đồ và nếu nó tồn tại, nó sẽ trả về một tham chiếu đến giá trị được lưu trữ. Nếu không, nó sẽ tạo một phần tử mới được chèn tại chỗ với khởi tạo mặc định và trả về một tham chiếu đến nó.

Các insertchức năng (trong hương vị yếu tố duy nhất) có một value_type( std::pair<const Key,Value>), nó sử dụng phím ( firstthành viên) và cố gắng để chèn nó. Bởi vì std::mapkhông cho phép trùng lặp nếu có một phần tử hiện có, nó sẽ không chèn bất cứ thứ gì.

Sự khác biệt đầu tiên giữa hai loại này là operator[]cần có khả năng xây dựng một giá trị khởi tạo mặc định và do đó không thể sử dụng được cho các loại giá trị không thể được khởi tạo mặc định. Sự khác biệt thứ hai giữa hai là những gì xảy ra khi đã có một phần tử với khóa đã cho. Các insertchức năng sẽ không thay đổi trạng thái của bản đồ, nhưng thay vì trả về một iterator tới phần tử (và falsechỉ ra rằng nó không được chèn).

// assume m is std::map<int,int> already has an element with key 5 and value 0
m[5] = 10;                      // postcondition: m[5] == 10
m.insert(std::make_pair(5,15)); // m[5] is still 10

Trong trường hợp insertđối số là một đối tượng value_type, có thể được tạo theo các cách khác nhau. Bạn có thể trực tiếp xây dựng nó với loại thích hợp hoặc vượt qua bất kỳ đối tượng nào value_typecó thể được xây dựng, đó là nơi std::make_pairphát huy tác dụng, vì nó cho phép tạo ra các std::pairđối tượng đơn giản , mặc dù nó có thể không phải là điều bạn muốn ...

Hiệu ứng ròng của các cuộc gọi sau là tương tự :

K t; V u;
std::map<K,V> m;           // std::map<K,V>::value_type is std::pair<const K,V>

m.insert( std::pair<const K,V>(t,u) );      // 1
m.insert( std::map<K,V>::value_type(t,u) ); // 2
m.insert( std::make_pair(t,u) );            // 3

Nhưng thực tế không giống nhau ... [1] và [2] thực sự tương đương nhau. Trong cả hai trường hợp, mã tạo ra một đối tượng tạm thời cùng loại ( std::pair<const K,V>) và truyền nó cho inserthàm. Các insertchức năng sẽ tạo ra các nút thích hợp trong cây tìm kiếm nhị phân và sau đó sao chép các value_typephần từ đối số đến nút. Ưu điểm của việc sử dụng value_typelà, value_typeluôn luôn khớp value_type , bạn không thể gõ nhầm loại std::pairđối số!

Sự khác biệt là trong [3]. Hàm std::make_pairnày là một hàm mẫu sẽ tạo ra một std::pair. Chữ ký là:

template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );

Tôi đã cố tình không cung cấp các đối số mẫu cho std::make_pair, vì đó là cách sử dụng phổ biến. Và hàm ý là các đối số mẫu được suy ra từ cuộc gọi, trong trường hợp này là T==K,U==V, do đó, cuộc gọi std::make_pairsẽ trả về một std::pair<K,V>(lưu ý thiếu const). Chữ ký đòi hỏi value_typeđó là gần nhưng không giống như giá trị trả về từ các cuộc gọi đến std::make_pair. Bởi vì nó đủ gần, nó sẽ tạo tạm thời đúng loại và sao chép khởi tạo nó. Điều đó sẽ lần lượt được sao chép vào nút, tạo ra tổng cộng hai bản sao.

Điều này có thể được sửa bằng cách cung cấp các đối số mẫu:

m.insert( std::make_pair<const K,V>(t,u) );  // 4

Nhưng đó vẫn là lỗi dễ xảy ra giống như cách gõ rõ ràng kiểu trong trường hợp [1].

Cho đến thời điểm này, chúng ta có những cách gọi khác nhau insertđòi hỏi phải tạo ra value_typebên ngoài và bản sao của đối tượng đó vào trong thùng chứa. Ngoài ra, bạn có thể sử dụng operator[]nếu loại mặc định có thể xây dựng và có thể gán được (chỉ tập trung chủ ý vào m[k]=v) và nó yêu cầu khởi tạo mặc định của một đối tượng và sao chép giá trị vào đối tượng đó.

Trong C ++ 11, với các mẫu matrixdic và chuyển tiếp hoàn hảo, có một cách mới để thêm các phần tử vào một thùng chứa bằng cách đặt (tạo tại chỗ). Các emplacehàm trong các thùng chứa khác nhau về cơ bản giống nhau: thay vì lấy một nguồn để sao chép vào vùng chứa, hàm sẽ lấy các tham số sẽ được chuyển tiếp đến hàm tạo của đối tượng được lưu trữ trong vùng chứa.

m.emplace(t,u);               // 5

Trong [5], cái std::pair<const K, V>không được tạo và chuyển đến emplace, mà là các tham chiếu đến đối tượng tuđược chuyển đến emplaceđó để chuyển tiếp chúng đến hàm tạo của value_typesubobject bên trong cấu trúc dữ liệu. Trong trường hợp này, không có bản sao std::pair<const K,V>nào được thực hiện cả, đó là lợi thế của emplacecác lựa chọn thay thế C ++ 03. Như trong trường hợp của insertnó sẽ không ghi đè giá trị trong bản đồ.


Một câu hỏi thú vị mà tôi đã không nghĩ tới là làm thế nào emplacethực sự có thể được thực hiện cho bản đồ và đó không phải là vấn đề đơn giản trong trường hợp chung.


5
Điều này được gợi ý trong câu trả lời, nhưng map [] = val sẽ ghi đè lên giá trị trước đó nếu có.
dk123

một câu hỏi thú vị hơn theo nghĩa của tôi, là nó phục vụ mục đích nhỏ. Bởi vì bạn lưu bản sao cặp, điều này là tốt vì không có bản sao cặp có nghĩa là không có mapped_typebản sao không đồng nhất. Những gì chúng ta muốn, là nơi xây dựng của mapped_typecặp và đặt nơi xây dựng cặp trong bản đồ. Do đó, cả std::pair::emplacechức năng và hỗ trợ chuyển tiếp của nó map::emplaceđều bị thiếu. Ở dạng hiện tại, bạn vẫn phải đưa mapped_type được xây dựng cho hàm tạo cặp sẽ sao chép nó một lần. nó tốt hơn hai lần, nhưng vẫn không tốt
v.oddou

Trên thực tế tôi đã sửa đổi nhận xét đó, trong C ++ 11 có một hàm tạo cặp mẫu phục vụ cùng một mục đích chính xác hơn là nơi trong trường hợp xây dựng 1 đối số. và một số cấu trúc piecewise kỳ lạ, như họ gọi nó, sử dụng bộ dữ liệu để chuyển tiếp các đối số, vì vậy chúng ta vẫn có thể chuyển tiếp hoàn hảo.
v.oddou

Có vẻ như có một lỗi hiệu suất của việc chèn vào unordered_map và map: link
De Khánh

1
Có thể rất hay để cập nhật thông tin này với thông tin trên insert_or_assigntry_emplace(cả từ C ++ 17), giúp lấp đầy một số lỗ hổng về chức năng từ các phương thức hiện có.
ShadowRanger

14

Emplace: Tận dụng lợi thế của tham chiếu giá trị để sử dụng các đối tượng thực tế mà bạn đã tạo. Điều này có nghĩa là không có lệnh sao chép hoặc di chuyển được gọi, tốt cho các đối tượng LỚN! Thời gian O (log (N)).

Chèn: Có quá tải cho tham chiếu giá trị chuẩn và tham chiếu giá trị, cũng như các trình lặp cho danh sách các phần tử cần chèn và "gợi ý" về vị trí của một phần tử. Việc sử dụng một trình lặp "gợi ý" có thể làm cho việc chèn thời gian giảm xuống thời gian tiếp theo, nếu không đó là thời gian O (log (N)).

Toán tử []: Kiểm tra xem liệu đối tượng có tồn tại hay không và nếu có, sửa đổi tham chiếu đến đối tượng này, nếu không, sử dụng khóa và giá trị được cung cấp để gọi make_ Pair trên hai đối tượng, và sau đó thực hiện cùng một chức năng như chức năng chèn. Đây là thời gian O (log (N)).

make_ Pair: Không nhiều hơn làm một cặp.

Không có "nhu cầu" để thêm emplace vào tiêu chuẩn. Trong c ++ 11 tôi tin rằng loại && tham chiếu đã được thêm vào. Điều này loại bỏ sự cần thiết cho ngữ nghĩa di chuyển và cho phép tối ưu hóa một số loại quản lý bộ nhớ cụ thể. Đặc biệt, tài liệu tham khảo giá trị. Toán tử chèn (value_type &&) bị quá tải không tận dụng được ngữ nghĩa in_place và do đó kém hiệu quả hơn nhiều. Mặc dù nó cung cấp khả năng xử lý các tham chiếu giá trị, nhưng nó bỏ qua mục đích chính của chúng, đó là xây dựng các đối tượng.


4
" Không có" nhu cầu "để thêm emplace vào tiêu chuẩn." Đây là giả dối. emplace()chỉ đơn giản là cách duy nhất để chèn một phần tử không thể sao chép hoặc di chuyển. (& vâng, có lẽ, để chèn một cách hiệu quả nhất một công cụ có bản sao và di chuyển các nhà xây dựng tốn kém hơn nhiều so với xây dựng, nếu điều đó tồn tại) Có vẻ như bạn cũng đã hiểu sai: đó không phải là " lợi dụng tham chiếu giá trị" để sử dụng các đối tượng thực tế mà bạn đã tạo "; không có đối tượng được tạo ra chưa, và bạn chuyển tiếp mapcác đối số cần để tạo ra nó bên trong chính nó. Bạn không làm cho đối tượng.
gạch dưới

10

Ngoài các cơ hội tối ưu hóa và cú pháp đơn giản hơn, một điểm khác biệt quan trọng giữa chèn và vị trí là cái sau cho phép chuyển đổi rõ ràng . (Đây là trên toàn bộ thư viện tiêu chuẩn, không chỉ dành cho bản đồ.)

Đây là một ví dụ để chứng minh:

#include <vector>

struct foo
{
    explicit foo(int);
};

int main()
{
    std::vector<foo> v;

    v.emplace(v.end(), 10);      // Works
    //v.insert(v.end(), 10);     // Error, not explicit
    v.insert(v.end(), foo(10));  // Also works
}

Đây được thừa nhận là một chi tiết rất cụ thể, nhưng khi bạn xử lý các chuỗi chuyển đổi do người dùng xác định, bạn nên ghi nhớ điều này.


Hãy tưởng tượng rằng foo yêu cầu hai int trong ctor của nó chứ không phải một. Bạn có thể sử dụng cuộc gọi này? v.emplace(v.end(), 10, 10); ... Hoặc bây giờ bạn sẽ cần sử dụng : v.emplace(v.end(), foo(10, 10) ); ?
Kaitain

Tôi không có quyền truy cập vào trình biên dịch ngay bây giờ, nhưng tôi sẽ cho rằng điều này có nghĩa là cả hai phiên bản sẽ hoạt động. Hầu như tất cả các ví dụ bạn thấy để emplacesử dụng một lớp có một tham số duy nhất. IMO nó thực sự sẽ làm cho bản chất của cú pháp dao động của emplace rõ ràng hơn nếu nhiều tham số được sử dụng trong các ví dụ.
Kaitain

9

Đoạn mã sau có thể giúp bạn hiểu "ý tưởng hình ảnh lớn" về sự insert()khác biệt của emplace():

#include <iostream>
#include <unordered_map>
#include <utility>

//Foo simply outputs what constructor is called with what value.
struct Foo {
  static int foo_counter; //Track how many Foo objects have been created.
  int val; //This Foo object was the val-th Foo object to be created.

  Foo() { val = foo_counter++;
    std::cout << "Foo() with val:                " << val << '\n';
  }
  Foo(int value) : val(value) { foo_counter++;
    std::cout << "Foo(int) with val:             " << val << '\n';
  }
  Foo(Foo& f2) { val = foo_counter++;
    std::cout << "Foo(Foo &) with val:           " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(const Foo& f2) { val = foo_counter++;
    std::cout << "Foo(const Foo &) with val:     " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(Foo&& f2) { val = foo_counter++;
    std::cout << "Foo(Foo&&) moving:             " << f2.val
              << " \tand changing it to:\t" << val << '\n';
  }
  ~Foo() { std::cout << "~Foo() destroying:             " << val << '\n'; }

  Foo& operator=(const Foo& rhs) {
    std::cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val
              << " \tcalled with lhs.val = \t" << val
              << " \tChanging lhs.val to: \t" << rhs.val << '\n';
    val = rhs.val;
    return *this;
  }

  bool operator==(const Foo &rhs) const { return val == rhs.val; }
  bool operator<(const Foo &rhs)  const { return val < rhs.val;  }
};

int Foo::foo_counter = 0;

//Create a hash function for Foo in order to use Foo with unordered_map
namespace std {
   template<> struct hash<Foo> {
       std::size_t operator()(const Foo &f) const {
           return std::hash<int>{}(f.val);
       }
   };
}

int main()
{
    std::unordered_map<Foo, int> umap;  
    Foo foo0, foo1, foo2, foo3;
    int d;

    //Print the statement to be executed and then execute it.

    std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
    umap.insert(std::pair<Foo, int>(foo0, d));
    //Side note: equiv. to: umap.insert(std::make_pair(foo0, d));

    std::cout << "\numap.insert(std::move(std::pair<Foo, int>(foo1, d)))\n";
    umap.insert(std::move(std::pair<Foo, int>(foo1, d)));
    //Side note: equiv. to: umap.insert(std::make_pair(foo1, d));

    std::cout << "\nstd::pair<Foo, int> pair(foo2, d)\n";
    std::pair<Foo, int> pair(foo2, d);

    std::cout << "\numap.insert(pair)\n";
    umap.insert(pair);

    std::cout << "\numap.emplace(foo3, d)\n";
    umap.emplace(foo3, d);

    std::cout << "\numap.emplace(11, d)\n";
    umap.emplace(11, d);

    std::cout << "\numap.insert({12, d})\n";
    umap.insert({12, d});

    std::cout.flush();
}

Đầu ra mà tôi nhận được là:

Foo() with val:                0
Foo() with val:                1
Foo() with val:                2
Foo() with val:                3

umap.insert(std::pair<Foo, int>(foo0, d))
Foo(Foo &) with val:           4    created from:       0
Foo(Foo&&) moving:             4    and changing it to: 5
~Foo() destroying:             4

umap.insert(std::move(std::pair<Foo, int>(foo1, d)))
Foo(Foo &) with val:           6    created from:       1
Foo(Foo&&) moving:             6    and changing it to: 7
~Foo() destroying:             6

std::pair<Foo, int> pair(foo2, d)
Foo(Foo &) with val:           8    created from:       2

umap.insert(pair)
Foo(const Foo &) with val:     9    created from:       8

umap.emplace(foo3, d)
Foo(Foo &) with val:           10   created from:       3

umap.emplace(11, d)
Foo(int) with val:             11

umap.insert({12, d})
Foo(int) with val:             12
Foo(const Foo &) with val:     13   created from:       12
~Foo() destroying:             12

~Foo() destroying:             8
~Foo() destroying:             3
~Foo() destroying:             2
~Foo() destroying:             1
~Foo() destroying:             0
~Foo() destroying:             13
~Foo() destroying:             11
~Foo() destroying:             5
~Foo() destroying:             10
~Foo() destroying:             7
~Foo() destroying:             9

Thông báo rằng:

  1. Một unordered_mapluôn luôn lưu trữ bên trong các Foođối tượng (và không, ví dụ, Foo *s) như các khóa, tất cả đều bị phá hủy khi unordered_mapbị phá hủy. Ở đây, các unordered_mapkhóa bên trong của foos là 13, 11, 5, 10, 7 và 9.

    • Về mặt kỹ thuật, chúng tôi unordered_mapthực sự lưu trữ std::pair<const Foo, int>các đối tượng, lần lượt lưu trữ các Foođối tượng. Nhưng để hiểu "ý tưởng hình ảnh lớn" về sự emplace()khác biệt như thế nào insert()(xem hộp được tô sáng bên dưới), bạn có thể tạm thời tưởng std::pairtượng đối tượng này là hoàn toàn thụ động. Khi bạn hiểu "ý tưởng hình ảnh lớn" này, điều quan trọng là phải sao lưu và hiểu cách sử dụng std::pairđối tượng trung gian này bằng cách unordered_mapgiới thiệu các kỹ thuật tinh tế, nhưng quan trọng.
  2. Chèn mỗi foo0, foo1foo2yêu cầu 2 cuộc gọi đến một trong những Foo's constructor sao chép / di chuyển và 2 cuộc gọi đến Foo' s destructor (như bây giờ tôi mô tả):

    a. Chèn từng foo0foo1tạo một đối tượng tạm thời ( foo4foo6, tương ứng) có hàm hủy của nó ngay lập tức được gọi sau khi chèn xong. Ngoài ra, các nội bộ của unordered_map Foo(là Foos 5 và 7) cũng có các hàm hủy của chúng được gọi khi unordered_map bị hủy.

    b. Để chèn foo2, thay vào đó, trước tiên chúng tôi rõ ràng đã tạo một đối tượng cặp không tạm thời (được gọi pair), được gọi Foolà hàm tạo sao chép trên foo2(tạo foo8như một thành viên nội bộ của pair). Sau đó, chúng tôi đã chỉnh sửa insert()cặp này, dẫn đến việc unordered_mapgọi lại hàm tạo sao chép (bật foo8) để tạo bản sao nội bộ của chính nó ( foo9). Như với foos 0 và 1, kết quả cuối cùng là hai lệnh gọi hàm hủy đối với phần chèn này với sự khác biệt duy nhất foo8là hàm hủy đó chỉ được gọi khi chúng ta kết thúc main()thay vì được gọi ngay sau khi insert()kết thúc.

  3. Việc đặt foo3kết quả chỉ dẫn đến 1 cuộc gọi sao chép / di chuyển của nhà xây dựng (tạo foo10nội bộ trong unordered_map) và chỉ có 1 cuộc gọi đến hàm Foohủy của. (Tôi sẽ quay lại vấn đề này sau).

  4. Đối với foo11, chúng tôi đã trực tiếp chuyển số nguyên 11 đến emplace(11, d)mức unordered_mapsẽ gọi hàm Foo(int)tạo trong khi thực thi nằm trong emplace()phương thức của nó . Không giống như trong (2) và (3), chúng tôi thậm chí không cần một số foođối tượng thoát trước để làm điều này. Điều quan trọng, lưu ý rằng chỉ có 1 cuộc gọi đến một nhà Fooxây dựng xảy ra (mà đã tạo foo11).

  5. Sau đó chúng tôi trực tiếp chuyển số nguyên 12 đến insert({12, d}). Không giống như emplace(11, d)(việc gọi lại chỉ dẫn đến 1 cuộc gọi đến hàm Footạo), cuộc gọi này insert({12, d})dẫn đến hai cuộc gọi đến hàm Footạo (tạo foo12foo13).

Điều này cho thấy sự khác biệt "bức tranh lớn" chính giữa insert()emplace()là:

Trong khi việc sử dụng insert() hầu như luôn đòi hỏi việc xây dựng hoặc tồn tại một số Foođối tượng trong main()phạm vi (theo sau là bản sao hoặc di chuyển), nếu sử dụng emplace()thì bất kỳ lệnh gọi nào đến hàm Footạo đều được thực hiện hoàn toàn trong unordered_map(nghĩa là trong phạm vi emplace()định nghĩa của phương thức). (Các) đối số cho khóa mà bạn chuyển đến emplace()được chuyển tiếp trực tiếp đến Foolệnh gọi của hàm tạo trong unordered_map::emplace()định nghĩa của nó (chi tiết bổ sung tùy chọn: trong đó đối tượng mới được xây dựng này được kết hợp ngay lập tức vào một trong unordered_mapcác biến thành viên để không có hàm hủy nào được gọi khi lá thực thi emplace()và không có nhà xây dựng di chuyển hoặc sao chép được gọi).

Lưu ý: Lý do " gần như " trong " hầu như luôn luôn " ở trên được giải thích trong I) bên dưới.

  1. tiếp tục: Lý do tại sao gọi điện thoại umap.emplace(foo3, d)gọi Foo's constructor sao chép không const như sau: Kể từ khi chúng tôi đang sử dụng emplace(), trình biên dịch biết rằng foo3(một tổ chức phi const Fooobject) được hiểu là một cuộc tranh cãi với một số Foonhà xây dựng. Trong trường hợp này, hàm tạo phù hợp nhất Foolà hàm tạo sao chép không phải là const Foo(Foo& f2). Đây là lý do tại sao umap.emplace(foo3, d)được gọi là một nhà xây dựng sao chép trong khi umap.emplace(11, d)không.

Phần kết:

I. Lưu ý rằng một quá tải insert()thực sự tương đương với emplace() . Như được mô tả trong trang cppreference.com này , tình trạng quá tải template<class P> std::pair<iterator, bool> insert(P&& value)(quá tải (2) của insert()trang cppreference.com này) tương đương với emplace(std::forward<P>(value)).

II. Đi đâu từ đây?

a. Chơi xung quanh với mã nguồn ở trên và tài liệu nghiên cứu cho insert()(ví dụ ở đây ) và emplace()(ví dụ ở đây ) được tìm thấy trực tuyến. Nếu bạn đang sử dụng một IDE như nhật thực hoặc NetBeans thì bạn có thể dễ dàng yêu cầu IDE của bạn cho bạn biết tình trạng quá tải insert()hoặc emplace()đang được gọi (trong nhật thực, chỉ cần giữ con trỏ chuột ổn định trong lệnh gọi trong giây). Dưới đây là một số mã để thử:

std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
//but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!

std::cout << "\numap.insert(std::pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<const Foo, int>({Foo::foo_counter, d}));
//The above uses Foo(int) and then Foo(const Foo &), as expected. but the
// below call uses Foo(int) and the move constructor Foo(Foo&&). 
//Do you see why?
std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
//Not only that, but even more interesting is how the call below uses all 
// three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy 
// constructors, despite the below call's only difference from the call above 
// being the additional { }.
std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});


//Pay close attention to the subtle difference in the effects of the next 
// two calls.
int cur_foo_counter = Foo::foo_counter;
std::cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where " 
  << "cur_foo_counter = " << cur_foo_counter << "\n";
umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});

std::cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
  << "Foo::foo_counter = " << Foo::foo_counter << "\n";
umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});


//umap.insert(std::initializer_list<std::pair<Foo, int>>({{Foo::foo_counter, d}}));
//The call below works fine, but the commented out line above gives a 
// compiler error. It's instructive to find out why. The two calls
// differ by a "const".
std::cout << "\numap.insert(std::initializer_list<std::pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(std::initializer_list<std::pair<const Foo, int>>({{Foo::foo_counter, d}}));

Bạn sẽ sớm thấy rằng quá tải của hàm std::pairtạo (xem tài liệu tham khảo ) cuối cùng được sử dụng unordered_mapcó thể có ảnh hưởng quan trọng đến số lượng đối tượng được sao chép, di chuyển, tạo và / hoặc phá hủy cũng như khi tất cả điều này xảy ra.

b. Xem điều gì xảy ra khi bạn sử dụng một số lớp container khác (ví dụ std::sethoặc std::unordered_multiset) thay vì std::unordered_map.

c. Bây giờ sử dụng một Goođối tượng (chỉ là một bản sao được đổi tên của Foo) thay vì một intloại phạm vi trong một unordered_map(nghĩa là sử dụng unordered_map<Foo, Goo>thay vì unordered_map<Foo, int>) và xem có bao nhiêu và hàm Gootạo nào được gọi. (Spoiler: có một hiệu ứng nhưng nó không ấn tượng lắm.)


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.