一 emplace() emplace_hint() try_emplace()区别
1. emplace
template< class... Args >
std::pair<iterator, bool> emplace( Args&&... args );
若容器中没有拥有该键的元素,则向容器插入以给定的 args 原位构造的新元素。
细心地使用 emplace
允许在构造新元素的同时避免不必要的复制或移动操作。 以与提供给 emplace
严格相同的实参,通过 std::forward<Args>(args)... 转发,调用新元素(即 std::pair<const Key, T>)的构造函数。 即使容器中已有拥有该关键的元素,也可能构造元素,该情况下新构造的元素将被立即销毁。
没有迭代器或引用会失效。
参数
args | - | 要转发给元素构造函数的实参 |
返回值
返回由指向被插入元素,或若不发生插入则为既存元素的迭代器,和指代插入是否发生的 bool(若发生插入则为 true,否则为 false)构成的 pair。
异常
如果因为任何原因抛出了异常,那么此函数无效果(强异常安全保证)。
复杂度
与容器大小成对数。
2. emplace_hint()
template< class... Args > | (C++11 起) | |
插入元素到尽可能靠近正好在 hint 之前的位置。原位构造元素,即不进行复制或移动操作。
与提供给函数的严格相同的实参,以 std::forward<Args>(args)... 转发之,调用元素类型( value_type
,即 std::pair<const Key, T>)的构造函数。
没有迭代器或引用会失效。
参数
hint | - | 指向将插入新元素到其前的位置的迭代器 |
args | - | 转发给元素构造函数的实参 |
返回值
返回指向新插入元素的迭代器。
若因为元素已存在而失败,则返回指向拥有等价键的既存元素的迭代器。
异常
若任何操作抛出异常,则此函数无效果(强异常保证)。
复杂度
通常与容器大小成对数,但若新元素被插入到恰于 hint 前则为均摊常数。
3. try_emplace()
template< class... Args > | (1) | (C++17 起) |
template< class... Args > | (2) | (C++17 起) |
template< class... Args > | (3) | (C++17 起) |
template< class... Args > | (4) | (C++17 起) |
向容器插入具有键 k 和以 args 构造的值的新元素,如果容器中没有这个键的元素。
1) 若容器中已存在等价于 k 的键,则不做任何事。否则行为类似 emplace,但以
value_type(std::piecewise_construct,
std::forward_as_tuple(k),
std::forward_as_tuple(std::forward<Args>(args)...)) 构造元素。
2) 若容器中已存在等价于 k 的键,则不做任何事。否则行为类似 emplace,但以
value_type(std::piecewise_construct,
std::forward_as_tuple(std::move(k)),
std::forward_as_tuple(std::forward<Args>(args)...)) 构造元素。
3) 若容器中已存在等价于 k 的键,则不做任何事。否则行为类似 emplace_hint,但以
value_type(std::piecewise_construct,
std::forward_as_tuple(k),
std::forward_as_tuple(std::forward<Args>(args)...)) 构造元素。
4) 若容器中已存在等价于 k 的键,则不做任何事。否则行为类似 emplace_hint,但以
value_type(std::piecewise_construct,
std::forward_as_tuple(std::move(k)),
std::forward_as_tuple(std::forward<Args>(args)...)) 构造元素。
没有迭代器或引用会失效。
参数
k | - | 用于查找和若找不到则插入的键 |
hint | - | 指向位置的迭代器,新元素将插入到其前 |
args | - | 转发给元素构造函数的实参 |
返回值
1,2) 同 emplace。
3,4) 同 emplace_hint。
复杂度
1,2) 同 emplace。
3,4) 同 emplace_hint。
注解
不同于 insert 或 emplace,若不发生插入,则这些函数不从右值实参移动,这令操纵值为仅移动类型的映射,如 std::map<std::string, std::unique_ptr<foo>> 更为容易。另外,try_emplace
分开处理键和给 mapped_type
的实参,这点不同于要求实参构造 value_type
(即一个 std::pair
)的 emplace。
上面主要是从cppreference拷贝的对接口的描述,接下来主要分享下这三个函数之前的部分区别:
4. 区别
这里从两个方面来考虑
a. 就地构造时传入参数的区别
首先我们来看个例子:
#include <iostream>
#include <memory>
#include <string>
#include <map>using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaa";
constexpr int maxSize = 10;class Widget {
public:Widget(ValueType value = dv) : value{value}{std::cout << "Widget(ValueType) constructor: " << this << std::endl;}Widget(const Widget& w) : value{w.value}{std::cout<<"copy constructor" << std::endl;}Widget& operator=(const Widget& rhs){if (this != &rhs){value = rhs.value;//assign new resource}std::cout << "copy assignment constructor" << std::endl;return *this;}Widget(Widget&& w) : value{std::move(w.value)}{std::cout << "move constructor" << std::endl;}Widget& operator=(Widget&& rhs){if (this != &rhs){value = std::move(rhs.value);//assign new resource}std::cout << "move assignment constructor" << std::endl;return *this;}~Widget(){if (value.empty()){std::cout << "0~destructor:" << this << std::endl;}else{value.clear();std::cout << "~destructor:" << this << std::endl;}}void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:ValueType value{};
};int main()
{auto up1 = std::make_unique<std::string>("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");auto up2 = std::move(up1);if (up1 != nullptr){std::cout << "up1 is not nullptr" << std::endl;}else{std::cout << "up1 is nullptr" << std::endl;}return 0;
}
在上面这个代码中,由于up1 移动给了up2,所以up1此时不能在使用,通过代码测试发现up1是nullptr;
接着我们来看下emplace、emplace_hint、try_emplace在插入键值已经存在的情况下的区别:
#include <iostream>
#include <memory>
#include <string>
#include <map>using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaa";
constexpr int maxSize = 10;class Widget {
public:Widget(ValueType value = dv) : value{value}{std::cout << "Widget(ValueType) constructor: " << this << std::endl;}Widget(const Widget& w) : value{w.value}{std::cout<<"copy constructor" << std::endl;}Widget& operator=(const Widget& rhs){if (this != &rhs){value = rhs.value;//assign new resource}std::cout << "copy assignment constructor" << std::endl;return *this;}Widget(Widget&& w) : value{std::move(w.value)}{std::cout << "move constructor" << std::endl;}Widget& operator=(Widget&& rhs){if (this != &rhs){value = std::move(rhs.value);//assign new resource}std::cout << "move assignment constructor" << std::endl;return *this;}~Widget(){if (value.empty()){std::cout << "0~destructor:" << this << std::endl;}else{value.clear();std::cout << "~destructor:" << this << std::endl;}}void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:ValueType value{};
};
void test1()
{std::map<int, Widget> m{};m.emplace(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");m.emplace(1, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");std::cout << std::endl;m.emplace_hint(m.end(), 1, "ccccccccccccccccccccccccccccccccccccccccc");std::cout << std::endl;m.try_emplace(1, "ddddddddddddddddddddddddddddddddddddddddd");
}
void test2()
{std::unique_ptr<Widget> up1 = std::make_unique<Widget>("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");std::unique_ptr<Widget> up2 = std::make_unique<Widget>("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");std::unique_ptr<Widget> up3 = std::make_unique<Widget>("ccccccccccccccccccccccccccccccccccccccccc");std::unique_ptr<Widget> up4 = std::make_unique<Widget>("ddddddddddddddddddddddddddddddddddddddddd");std::map<int, std::unique_ptr<Widget>> m{};m.emplace(1, std::move(up1));if (up1 != nullptr){std::cout << "up1 is not nullptr" << std::endl;}else{std::cout << "up1 is nullptr" << std::endl;}m.emplace(1, std::move(up2));if (up2 != nullptr){std::cout << "up2 is not nullptr" << std::endl;}else{std::cout << "up2 is nullptr" << std::endl;}m.emplace_hint(m.end(), 1, std::move(up3));if (up3 != nullptr){std::cout << "up3 is not nullptr" << std::endl;}else{std::cout << "up3 is nullptr" << std::endl;}m.try_emplace(1, std::move(up4));if (up4 != nullptr){std::cout << "up4 is not nullptr" << std::endl;}else{std::cout << "up4 is nullptr" << std::endl;}
}
int main()
{//test1();test2();return 0;
}
output:
从上述代码结果可以看出 对于emplace和emplace_hint来说,键值已经存在的情况下任然会move参数的数据,而try_emplace在键值已经存在的情况下,不会移动(move)参数的数据;
b.性能的区别:
如果在就地构造不存在键值的数据时,带有hint迭代器的效率有些情况下要比不带hint迭代器的效率高:这是因为带有hint的已经查找过一次map表了,而不带hint的还需要再次查找map。
#include <iostream>
#include <string>
#include <map>
#include <chrono>
using ValueType = std::string;
const ValueType dv = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
constexpr unsigned long int maxSize = 999999;class Widget {
public:Widget(ValueType value = dv) : value{value}{std::cout << "Widget(ValueType) constructor: " << this << std::endl;}Widget(const Widget& w) : value{w.value}{std::cout<<"copy constructor" << std::endl;}Widget& operator=(const Widget& rhs){if (this != &rhs){value = rhs.value;//assign new resource}std::cout << "copy assignment constructor" << std::endl;return *this;}Widget(Widget&& w) : value{std::move(w.value)}{std::cout << "move constructor" << std::endl;}Widget& operator=(Widget&& rhs){if (this != &rhs){value = std::move(rhs.value);//assign new resource}std::cout << "move assignment constructor" << std::endl;return *this;}~Widget(){if (value.empty()){std::cout << "0~destructor:" << this << std::endl;}else{value.clear();std::cout << "~destructor:" << this << std::endl;}}void print(){std::cout << "value:" << value << " : " << this << std::endl;}
private:ValueType value{};
};void logTime(std::string name, std::chrono::high_resolution_clock::time_point tp1, std::chrono::high_resolution_clock::time_point tp2)
{std::chrono::duration<size_t, std::nano> dur = tp2 - tp1;std::cout << name << " : " << std::chrono::duration_cast<std::chrono::nanoseconds>(dur).count() << std::endl;
}int main()
{std::map<unsigned long int, std::string> m1{};std::map<unsigned long int, std::string> m2{};std::map<unsigned long int, std::string> m3{};std::map<unsigned long int, std::string> m4{};std::chrono::high_resolution_clock::time_point tp1 = std::chrono::high_resolution_clock::now();for(unsigned long int i = 0; i < maxSize; ++i){if (m1.end() == m1.find(i)){m1.emplace(i, dv);}}std::chrono::high_resolution_clock::time_point tp2 = std::chrono::high_resolution_clock::now();for(unsigned long int i = 0; i < maxSize; ++i){if (auto iter = m2.find(i); m2.end() == iter){m2.emplace_hint(iter, i, dv);}}std::chrono::high_resolution_clock::time_point tp3 = std::chrono::high_resolution_clock::now();for(unsigned long int i = 0; i < maxSize; ++i){if (m3.end() == m3.find(i)){m3.try_emplace(i, dv);}}std::chrono::high_resolution_clock::time_point tp4 = std::chrono::high_resolution_clock::now();for(unsigned long int i = 0; i < maxSize; ++i){if (auto iter = m4.find(i); m4.end() == iter){m4.try_emplace(iter, i, dv);}}std::chrono::high_resolution_clock::time_point tp5 = std::chrono::high_resolution_clock::now();logTime("emplace() ", tp1, tp2);logTime("emplace_hint() ", tp2, tp3);logTime("try_emplace() ", tp3, tp4);logTime("try_emplace(hint)", tp4, tp5);return 0;
}