《Effective STL》条款3-条款4

本文探讨了在容器中高效管理对象拷贝与利用STL容器优化性能的方法,包括使用`empty`替代`size()`检查空容器,以及如何避免不必要的拷贝与分割问题,旨在提高程序运行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

条款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,是一个明智的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值