《Effective STL》条款3-条款4

条款3:使容器里对象的拷贝操作轻量而正确

容器可以存储对象。当向容器添加对象(insert或push_back),添加到容器的对象是指定对象的拷贝;同理,取出对象时也是通过拷贝。

存储在容器里的对象,还可能会对它拷贝。例如,从序列容器插入或删除元素,会引起元素的移动,移动是通过拷贝进行的(条款 14、15)。如果使用了排序算法(条款 31)next_permutation、previous_permutation、remove、unique等或同类算法(条款 32),rotate或reverse等,对象也会拷贝(移动)。

对象的拷贝是通过拷贝构造函数和赋值操作符完成。关于构造函数,可以参考这里

因为会发生拷贝,如果这个拷贝过程比较“昂贵”,那么这可能会是性能的瓶颈。容器中的对象越多,那么就很可能在拷贝上消耗更大的代价。此外,还有一个非传统意义上的“拷贝”对象,把这样的对象放进容器会导致不幸(例子参考条款 8)。

因为继承的存在,拷贝时可能会发生分割。即,如果用基类对象建立容器,而插入派生类对象,这时通过基类的拷贝构造函数插入,派生类对象会被切割为基类对象。

vector<Widget> vw; 
class SpecialWidget:public Widget {...};    // SpecialWidget从上面的Widget派生

SpecialWidget sw; 
vw.push_back(sw);       // sw被当作基类对象拷入vw
            // 当拷贝时它的特殊部分丢失了

避免上面的问题的一个解决方法是建立指针容器,这样拷贝更快,且没有分割问题。但是指针容器本身也有问题(条款 7、33)。要避免这个问题的办法是建立智能指针的容器(条款 7)。

STL虽然进行了大量拷贝,但它设计为避免不必要的拷贝。和数组做个比较:

Widget w[maxNumWidgets];        // 建立一个大小为maxNumWidgets的Widgets数组
                // 默认构造每个元素

上面数组中,每个数组对象都使用构造函数构造了;但有时我们不会使用全部数组对象,或使用前重新给它赋值,最开始的构造是没必要的。这时可以通过STL来代替:

vector<Widget> vw;          // 建立一个0个Widget对象的vector
                // 需要的时候可以扩展

STL中的vector是动态开辟内存,如果确定元素个数,可以给它预先分配内存

vector<Widget> vw; 
vw.reserve(maxNumWidgets);      // reserve的详细信息请参见条款14

条款4:用empty来代替检查size()是否为0

对于任意容器c

if(c.size()==0)

本质上等价于

if(c.empty())

empty的典型实现是一个返回size是否为0的内联函数。但首选应该是empty,因为对于所有标准容器,empty是一个常数时间操作,但对于list,size的花费为线性时间。

list之所以不能提供常数时间的size实现,是因为list特有的splice有很多要处理的东西。例如:

list<int> list1; 
list<int> list2; 
... 
list1.splice(                   // 把list2中
    list1.end(), list2,         // 从第一次出现5到
    find(list2.begin(), list2.end(), 5),        // 最后一次出现10
    find(list2.rbegin(), list2.rend(), 10).base()   // 的所有节点移到list1的结尾。
);                      // 关于调用的
                        // "base()"的信息,请参见条款28

上面这段代码假设了list2在5后面有个10。执行完上面代码后,list1中有多少元素,在遍历find(list2.begin(), list2.end(), 5)和find(list2.rbegin(), list2.rend(), 10).base()之间有多少元素之前,无法得知list1中元素个数。

list中如果把size设计成常数时间操作,那么list成员函数在更新list时也要更新size的大小,包括splice。这时splice就是线性时间操作了。size和splice不能都是常数时间操作,必须有一个让步。

不同list实现用不同方式解决这个矛盾,依赖于它的作者是让size或splice的区间形势达到最高效率。即使在一个平台上,size是常数时间,但是代码可能还会移植到另一个平台,不同平台STL实现可能不同。

因此,用empty代替size()==0,是一个明智的选择。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STL和标准 我经常提及C++标准,因为《Effective STL》专注于可移植的,与标准一致的C++。理论上,我在这本书里演示的一切都可以用于每个C++实现。实际上,那不是真的。编译器的缺陷和STL实现凑合成防止一些有效的代码编译或表现出它们应该有的行为。那是很常见的情况,我描述了这些问题,而且解释了你应该怎么变通地解决他们。 有时候,最容易的变通办法是使用另一个STL实现。附录B给一个这种情况的例子。实际上,STL用得越多,编译器和库实现的区别就越重要。程序员在设法让合法的代码编译时遇到困难,他们通常责备他们的编译器,但对于STL,编译器可能是好的,而STL实现是不良的。为了强调你得依赖编译器和库实现的事实,我使用你的STL平台。一个STL平台是一个特定编译器和一个标准模板库特定实现的组合。在本书里,如果我提及一个编译器问题,你能确信我意思是编译器有问题。但是,如果我说你的STL平台有问题,你应该理解为“可能是编译器缺陷,可能是库缺陷,或许都有”。 我一般提及你的“编译器们”——复数。那是我长期相信你通过确保代码可以在多于一个的编译器上工作的方法来改进你的代码质量(特别是移植性)的产物。此外,使用多个编译器一般可以简化拆解由STL的使用不当造成的错误信息难题。(条款49致力于解码此类消息的方法。) 关于与标准一致的代码,我强调的另一个方面是你应该避免构造未定义行为。这样的构造可能在运行期做任何事情。不幸的是,这意味着它们可能正好做了你想要的,而那会导致一种错误的安全感。太多程序员以为未定义行为总会导致一个明显的问题,例如,一个分段错误或其他灾难性的错误。未定义行为的结果实际上更为狡猾,例如,破坏极少引用的数据。它们也可以通过程序运行。我发现一个未定义行为的好定义是“为我工作,为你工作,在开发和QA期间工作,但在你最重要的用户面前爆炸了”。避免未定义行为很重要,所以我指出了它会出现的通常情况。你应该训练你自己警惕这样的情况。
effective stl pdf 怎么使用stl 这里几乎都有说明 条款1: 仔细选择你要的容器 条款2: 小心对“容器无关代码”的幻想 条款3: 使容器里对象的拷贝操作轻量而正确 条款4: 用empty来代替检查size是否为0 条款5: 尽量使用范围成员函数代替他们的单元素兄弟 条款6: 警惕C++的及其令人恼怒的分析 条款7: 当使用new得指针的容器时,切记在容器销毁前delete那些指针 条款8: 千万不要把auto_ptr放入容器条款9: 小心选择删除选项 条款10: 当心allocator的协定和约束 条款11: 了解自定义allocator的正统使用法 条款12: 对STL容器的线程安全性的期待现实一些 vector和string 条款13: 尽量使用vector和string来代替动态申请的数组 条款14: 用reserve来避免不必要的内存重新分配 条款15: 当心string的实现中的变化 条款16: 如何将vector和string的数据传给传统的API 条款17: 用“交换技巧”来修正过度的容量 条款18: 避免使用vector<bool> 关联容器 条款19: 了解相等和等价的区别 条款20: 为包含指针的关联容器指定比较类型 条款21: 永远让比较函数对相等的值返回false 条款22: 避免对set和multiset的键值进行修改 条款23: 考虑用排序的vector代替关联容器 条款24: 当效率很关键时尽量用map::insert代替map::operator 条款25: 让自己熟悉非标准的hash容器 迭代器 条款26: 尽量使用iterator代替const_iterator,reverse_iterator和const_reverse_iterator 条款27: 使用distance和advance把const_iterators转化成iterators 条款28: 了解如何通过reverse_iterator的base得到iterator 条款29: 需要一字符一字符输入时请用istreambuf_iterator 算法 条款30: 确保目的范围足够大 条款31: 了解你的排序选项 条款32: 如果你真的想删除东西的话在remove-like的算法后紧接上erase 条款33: 当心在包含指针的容器使用remove-like的算法 条款34: 注意哪些算法需要排序过的范围 条款35: 通过mismatch或lexicographical_compare实现简单的忽略大小写字符串比较 条款36: 用not1和remove_copy_if来表现copy_if 条款37: 用accumulate或for_each来统计序列 仿函数,仿函数类,函数等等 条款38: 把仿函数类设计成值传递的 条款39: 用纯函数做predicate 条款40: 增强仿函数类的适应性 条款41: 明确ptr_fun, mem_fun和mem_fun_ref的区别 条款42: 保证less是operator<的意思 用STL编程 条款43: 尽量用算法调用代替手写循环 条款44: 尽量用成员函数代替同名的算法 条款45: 注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别 条款46: 考虑用函数对象代替函数作为算法的参数 条款47: 避免产生只写代码 条款48: 总是#include适当的头文件 条款49: 学会破解STL相关的编译器出错信息 条款50: 让自己熟悉STL相关的网站
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值