C++ 你应该考虑置入操作(emplace)而非插入操作————C++2.0知识补充
2021/11/29 22:06:57
本文主要是介绍C++ 你应该考虑置入操作(emplace)而非插入操作————C++2.0知识补充,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
C++2.0知识补充
文章目录
- C++2.0知识补充
- 1 某些情况下考虑置入而非插入
- 1.1 拥有置入操作的容器
- 1.2 置入操作占优的条件
- 1.3 注意事项
1 某些情况下考虑置入而非插入
1.1 拥有置入操作的容器
- 1,
emplace_back
可用于任何支持push_back
的标准容器; - 2,
emplace_front
可用于任何支持push_front
的标准容器; - 3,任何支持插入操作(除了
std::forward_list
和std::array
以外的所有标准容器)都支持置入操作; - 4,关联容器提供了
emplace_hint
来补充带有hint
迭代器的insert函数; - 5,
std::forward_list
也有emplace_after
来补充insert_after
1.2 置入操作占优的条件
在以下条件成立时,置入函数极可能运行的更快:
- 1,待添加的值是以构造而非赋值方式加入容器;
- 基于节点的容器几乎总是使用构造来添加新值,以及不基于节点的
std::vector
,std::deque
和std::string
(std::array
不基于节点,但也不支持插入和置入)。 - 在非基于节点的容器中,
emplace_back
使用构造非赋值降新值就位,std::deque
的emplace_front
也成立。
- 基于节点的容器几乎总是使用构造来添加新值,以及不基于节点的
- 2,传递的实参类型与容器持有之物的类型不同;
- 只有将类型不同的元素插入到容器中时,才会创建和析构临时对象,才能体现置入操作的优点。
- 3,容器不会由于存在重复值而拒绝待添加的值。
- 因为如果容器不允许重复值,那么置入操作将会用新值创建一个节点与容器现有节点比较,如果该值已存在,则置入操作就会中止,节点也会被析构,那就意味着构造和析构的成本都被浪费了。
条款1的示例代码如下:
有效率差(创建临时对象):
std::vector<std::string> vs; //push_back版本 vs.push_back("xyz") //编译器实际看到的版本 //创建std::string类型临时对象,并将其传递给push_back vs.push_back(std::string("xyc")) //emplace_back版本 //直接从xyz出发在vs内构造std::string类型对象 vs.emplace_back("xyz")
效果相同的示例(不需要创建临时对象,也不需要虚构临时对象):
std::vector<std::string> vs; std::string str("hello") vs.push_back(str) vs.emplace_back(str)
以赋值方式加入容器(优势消失):
std::vector<std::string> vs; vs.emplace(vs.begin(), "xyz");
1.3 注意事项
是否选用置入函数,还有两个问题值得考虑:
- 1,资源相关的问题(在操作资源管理对象容器时,可能会造成内存泄漏【不应该将
new 对象名
这样的表达式传递给emplace_back
、push_back
等大多数函数】); - 2,置入函数可能会执行在插入函数中会被拒绝的类型(与带有explicit声明修饰的析构函数之间的互动)【确保传递正确的参数】。
条款1示例代码:
调用
push_back
:创建std::shared_ptr<Widget>
临时对象,用于持有从new Widget
返回的裸指针,即使内存不足,插入失败,std::shared_ptr
的析构函数也会释放临时对象;
调用emplace_back
版本:new Widget
返回的裸指针被完美转发,但是如果内存不足,置入失败,指向Widget
对象的指针丢失,堆上的Widget
对象将造成泄漏。
class Widget{}; void killWidget(Widget* pWidget){} int main(){ std::list<std::shared_ptr<Widget>> ptrs; //push_back版本 ptrs.push_back(std::shared_ptr<Widget>(new Widget, killWidget)); ptrs.push_back({new Widget, killWidget}); //emplace_back版本 ptrs.emplace_back(std::shared_ptr<Widget>(new Widget, killWidget)); ptrs.emplace_back(new Widget, killWidget); }
条款一的解决方法(但是emplace_back
和push_back
版本差别不大):
class Widget{}; void killWidget(Widget* pWidget){} int main(){ std::list<std::shared_ptr<Widget>> ptrs; std::shared_ptr<Widget> spw(new Widget, killWidget);//构造Widget并用spw管理 //push_back版本 //ptrs.push_back(std::move(spw)); //emplace_back版本 ptrs.emplace_back(std::move(spw)); }
条款2示例代码:
调用
push_back
:编译失败,因为const char*
指针类型的std::regex
构造函数以explicit
声明,类型转换就被阻止了。
调用emplace_back
:向std::regex
构造函数传递的是个构造函数的实参(并不被视为隐式类型转换),编译器看来等同于std::regex r(nullptr);
,这样虽然可以通过编译,但是std::regex
接受的却是一个没有意义空指针,问题将会被隐藏。
//下一行代码无法通过编译,因为复制初始化禁止使用那个构造函数 std::vector<std::regex> regexs; //regexs.push_back(nullptr);//error: no matching member function for call to 'push_back' //下面代码正常通过编译 //直接初始化允许使用接受指着的、带有explicit声明的std::regex构造函数 regexs.emplace_back(nullptr);
编译结果的详细解释:push_back
和emplace_back
分别对应复制初始化和直接初始化,而复制初始化不允许调用带有explicit
声明修饰的构造函数。
//复制初始化 std::regex r1 = nullptr;//报错 //直接初始化 std::regex r2(nullptr);
这篇关于C++ 你应该考虑置入操作(emplace)而非插入操作————C++2.0知识补充的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享