C++:探索std::map和std::unordered_map中的新增操作
std::map和std::unordered_map主要提供如下幾種新增操作:
- try_emplace () (C++17)
- emplace ()
- insert()
- [] =
下面給出一段測試程式碼,觀察物件在新增到std::map中時,構造物件過程中會有什麼區別:
#include <map> #include <iostream> #include <tuple> #include <exception> #include <string> #include <memory> #include <vector> using namespace std; class Kid { private: static int cnt; int id; public: string name; int age; Kid() { id = ++cnt; cout << "Kid()" << id << endl; } Kid(string _name, int _age) : name(_name), age(_age) { id = ++cnt; cout << "Kid(string _name, int _age)" << id << endl; } Kid(const Kid&) { id = ++cnt; cout << "Kid(const Kid&)" << id << endl; } Kid(Kid&&) { id = ++cnt; cout << "Kid(Kid&&)" << id << endl; } Kid& operator=(const Kid&) { cout << "operator=(const Kid&)" << id << endl; return *this; } bool operator< (const Kid& rhs) const { return this->age < rhs.age; } }; int Kid::cnt = 0; class People { private: static int cnt; int id; public: string name; int age; People() { id = ++cnt; cout << "People()" << id << endl; } People(string _name, int _age) : name(_name), age(_age) { id = ++cnt; cout << "People(string _name, int _age)" << id << endl; } People(const People&) { id = ++cnt; cout << "People(const People&)" << id << endl; } People(People&&) { id = ++cnt; cout << "People(People&&)" << id << endl; } People& operator=(const People&) { cout << "operator=(const People&)" << id << endl; return *this; } }; int People::cnt = 0; int main() { map<Kid, People> m; cout << "--------------------------------" << endl; { cout << "--------------------------------//(1)" << endl; Kid x1("xxl", 1213332); m.try_emplace(x1, "xlx", 121); } { cout << "--------------------------------//(2)" << endl; m.try_emplace(Kid("xxl", 1213332), "xlx", 121); } { cout << "--------------------------------//(3)" << endl; Kid x1("xxl", 1213332); People p1("xlx", 121); m.emplace(x1, p1); } { cout << "--------------------------------//(4)" << endl; m.emplace(Kid("xxl", 1213332), People("aaaa", 121)); } { cout << "--------------------------------//(5)" << endl; m.emplace(make_pair(Kid("xxl", 1213332), People("aaaa", 121))); } { cout << "--------------------------------//(6)" << endl; m.emplace(std::piecewise_construct, std::forward_as_tuple("fffff", 999), std::forward_as_tuple("wwww", 121)); } { cout << "--------------------------------//(7)" << endl; m.insert(make_pair(Kid("xxl", 1213332), People("aaaa", 121))); } { cout << "--------------------------------//(8)" << endl; m[Kid("xxl", 1213332)]= People("aaaa", 121); } cout << "--------------------------------" << endl; return 1; }
VC++2017的執行結果:
--------------------------------
--------------------------------//(1)
Kid(string _name, int _age)3
Kid(const Kid&)4
People(string _name, int _age)3
--------------------------------//(2)
Kid(string _name, int _age)5
Kid(Kid&&)6
People(string _name, int _age)4
--------------------------------//(3)
Kid(string _name, int _age)7
People(string _name, int _age)5
Kid(const Kid&)8
People(const People&)6
--------------------------------//(4)
People(string _name, int _age)7
Kid(string _name, int _age)9
Kid(Kid&&)10
People(People&&)8
--------------------------------//(5)
People(string _name, int _age)9
Kid(string _name, int _age)11
Kid(Kid&&)12
People(People&&)10
Kid(Kid&&)13
People(People&&)11
--------------------------------//(6)
Kid(string _name, int _age)14
People(string _name, int _age)12
--------------------------------//(7)
People(string _name, int _age)13
Kid(string _name, int _age)15
Kid(Kid&&)16
People(People&&)14
Kid(Kid&&)17
People(People&&)15
--------------------------------//(8)
People(string _name, int _age)16
Kid(string _name, int _age)18
Kid(Kid&&)19
People()17
operator=(const People&)4
--------------------------------
從以上執行結果中可以看到,只講物件構造效率的話,效率從高到低依次為:
(6) > (2) > (4) > (5) (7) > (1) > (3) > (8)
其中(6)只調用了2次建構函式,(2)呼叫了2次建構函式和1次轉移建構函式,(4)呼叫了2次建構函式和2次轉移建構函式,(5)(7)呼叫了2次建構函式和4次轉移建構函式,(1)呼叫了2次建構函式和1次拷貝建構函式,(3)呼叫了2次建構函式和2次拷貝建構函式,(8)呼叫了3次建構函式、1次拷貝建構函式和1次賦值函式。但物件構造效率高低還不能決定新增操作的效率,即新增操作的效率排名不一定退以上排名一樣。
下面對以上幾種方式分別進行程式碼測試,程式碼如下:
#include <iostream>
#include <sstream>
#include <fstream>
#include <tuple>
#include <exception>
#include <string>
#include <vector>
#include <functional>
#include <array>
#include <chrono>
#include <unordered_map>
#include <map>
#include <cstdlib>
#include <ctime>
#include <climits>
using namespace std;
class Kid_ {
private:
static int cnt;
int id;
public:
string name;
int money;
Kid_() {
id = ++cnt;
}
Kid_(string& _name, int& _money) : name(_name), money(_money) {
id = ++cnt;
}
Kid_(const Kid_& k) : name(k.name), money(k.money) {
id = ++cnt;
}
Kid_(Kid_&& k) : name(move(k.name)), money(move(k.money)) {
id = ++cnt;
}
bool operator< (const Kid_& rhs) const {
return this->name < rhs.name;
}
};
int Kid_::cnt = 0;
class People_ {
private:
static int cnt;
int id;
public:
string name;
int money;
People_() {
id = ++cnt;
}
People_(string& _name, int& _money) : name(_name), money(_money) {
id = ++cnt;
}
People_(const People_& p) : name(p.name), money(p.money) {
id = ++cnt;
}
People_(People_&& p) : name(move(p.name)), money(move(p.money)) {
id = ++cnt;
}
};
int People_::cnt = 0;
struct KeyHash {
std::size_t operator()(const Kid_& k) const
{
return std::hash<string>()(k.name) ^ k.money;
}
};
struct KeyEqual {
bool operator()(const Kid_& lhs, const Kid_& rhs) const
{
return lhs.name == rhs.name && lhs.money == rhs.money;
}
};
string getRandomString(int length) {
string ret;
for (int i = 0; i < length; i++) {
int r = std::rand() % 10;
if (i == 0 && r ==0) {
i--;
continue;
}
ret += to_string(r);
}
return ret;
}
void testMapAdd() {
std::srand(std::time(0));
int times = 1000000;
cout << "--------------------------------//map" << endl;
{
long long timeUsed = 0;
map<Kid_, People_ > m;
cout << "--------------------------------//(1)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
Kid_ k(s1, r1);
m.try_emplace(k, s2, r2);
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
map<Kid_, People_ > m;
cout << "--------------------------------//(2)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m.try_emplace(Kid_(s1, r1), s2, r2);
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
map<Kid_, People_ > m;
cout << "--------------------------------//(3)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
Kid_ x1(s1, r1);
People_ p1(s2, r2);
m.emplace(x1, p1);
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
map<Kid_, People_ > m;
cout << "--------------------------------//(4)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m.emplace(Kid_(s1, r1), People_(s2, r2));
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
map<Kid_, People_ > m;
cout << "--------------------------------//(5)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m.emplace(make_pair(Kid_(s1, r1), People_(s2, r2)));
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
map<Kid_, People_ > m;
cout << "--------------------------------//(6)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m.emplace(std::piecewise_construct, std::forward_as_tuple(s1, r1), std::forward_as_tuple(s2, r2));
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
map<Kid_, People_ > m;
cout << "--------------------------------//(8)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m[Kid_(s1, r1)] = People_(s2, r2);
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
/////////////////////////////////////////////////////////////////////////////////////////
cout << "--------------------------------//unordered_map" << endl;
{
long long timeUsed = 0;
unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times);
cout << "--------------------------------//(1)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
Kid_ k(s1, r1);
m.try_emplace(k, s2, r2);
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times);
cout << "--------------------------------//(2)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m.try_emplace(Kid_(s1, r1), s2, r2);
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times);
cout << "--------------------------------//(3)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
Kid_ x1(s1, r1);
People_ p1(s2, r2);
m.emplace(x1, p1);
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times);
cout << "--------------------------------//(4)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m.emplace(Kid_(s1, r1), People_(s2, r2));
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times);
cout << "--------------------------------//(5)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m.emplace(make_pair(Kid_(s1, r1), People_(s2, r2)));
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times);
cout << "--------------------------------//(6)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m.emplace(std::piecewise_construct, std::forward_as_tuple(s1, r1), std::forward_as_tuple(s2, r2));
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
{
long long timeUsed = 0;
unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times);
cout << "--------------------------------//(8)" << endl;
for (int i = 0; i < times; i++) {
string s1 = getRandomString(10);
int r1 = std::rand();
string s2 = getRandomString(10);
int r2 = std::rand();
auto start = std::chrono::system_clock::now();
m[Kid_(s1, r1)] = People_(s2, r2);
auto end = std::chrono::system_clock::now();
timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl;
}
}
int main() {
testMapAdd();
return 0;
}
執行結果:
--------------------------------//map
--------------------------------//(1)
934167100: 934.167
--------------------------------//(2)
892646600: 892.647
--------------------------------//(3)
919973300: 919.973
--------------------------------//(4)
917704500: 917.705
--------------------------------//(5)
935676700: 935.677
--------------------------------//(6)
946283500: 946.284
--------------------------------//(8)
939161000: 939.161
--------------------------------//unordered_map
--------------------------------//(1)
269299400: 269.299
--------------------------------//(2)
278339800: 278.34
--------------------------------//(3)
269483600: 269.484
--------------------------------//(4)
273019500: 273.02
--------------------------------//(5)
274466600: 274.467
--------------------------------//(6)
267864800: 267.865
--------------------------------//(8)
273746400: 273.746
從數值上看,這幾種方式的效率相差並不大,而且每次執行的結果的排名都不完全相同。這應該是編譯器做了大量的編譯優化,所以執行效率都還是不錯的。