[C++]高效使用容器的一些建议

高效使用容器的一些建议

本文介绍一些在使用容器中常见的问题,并给出其解决方法从而提升对容器的认识和使用。

1. 不要试图编写独立于容器类型的代码

STL是以泛化原则为基础的:数组被泛化为”以其包含的对象的类型为参数“的容器;函数被泛化为“以其使用的迭代器的类型为参数”的算法;指针被泛化为“以其指向的对象的类型为参数”的迭代器。

如果我们试图编写独立于容器类型的代码,例如编写一个既能够满足序列容器又满足关联容器的代码,我们最后会发现我们使用的只是他们功能交集而已,而这个交集几乎没有什么用。

但是对于客户代码却不见得如此。对于客户代码为了提高可维护性,我们需要使用封装的技术。最简单的方法就是通过对容器类型和其迭代器类型使用类定义。因此,不要写:

class Widget {...};
vector<Widget> vw;
Widget bestWidget;
//  assign a value to bestWidget
....
vector<Widget>::iterator i = find(vw.begin(), vw.end(), bestWidget);

而要写成:

class Widget {
  ...};
typedef vector<Widget> WidgetContainer;
WidgetContainer cw;
Widget bestWidget;
...
WidgetContainer::iterator i = find(cw.begin(), cw.end(), bestWidget);

如此,当你想要改变容器类型时,就会简单很多。尤其是当这种改变只是增加一个自定义的分配子时,就显得更加方便了。

class Widget {
  ...};
template <typename T>
SpecialAllocator { ... };
typedef vector<Widget SpecialAllocator<Widget>> WidgetContainer;
WidgetContainer cw;
Widget bestWidget;
...
WidgetContainer::iterator i = find(cw.begin(), cw.end(), bestWidget);

类型定义只不过是其他类型的别名,所以它带来的封装纯粹是词法上的,但可以很有效地减少代码量。

类型定义并不能阻止客户去做他们原本无法做到的事情。如果你不想把自己选择的容器暴露给客户,就得使用类。例如把容器隐藏在一个类中,并尽量减少那些通过类接口可见的,与容器相关的信息。

class CustomerList {
    private:
        typedef list<Customer> CustomerContainer;
        typedef CustomerConatiner::iterator CCIterator;
        ...
        CustomerContainer Customers;
    public:
        ...
};

当你需要改变类的容器时,需要注意检查类的相关函数是否会受到影响。

2. 确保容器中的对象拷贝正确而高效

容器中保存了对象,但并不是你提供给那些容器的对象,而是他们的拷贝。

无论是对象的赋值,移动,排序都是基于拷贝来实现的。拷贝对象就是STL的工作方式。

值得注意的是,对于存在继承关系的情况下,拷贝动作会导致剥离(slicing)。也就是说,如果你创建了一个存放基类对象的容器,却向其中放入派生类的对象,那么派生类的对象被拷贝进容器时,它的特有部分将会丢失

剥离问题意味着向含有基类对象的容器中放入派生类对象总是错误的,如果你希望其正确的使用,那么应该使用存放基类指针。(动态绑定)

容器的设计思想是为了避免不必要的拷贝,它的总体目标总是避免创建不必要的对象。例如在内置数组中,只要指定了大小,它总是会调用缺省构造函数来使每一个对象实例化,但对于容器却不一定会如此。

#include <iostream>
#include <vector>
using namespace std;
class Widget {
public:
    Widget() {
        cout << "Widget constructed!" << endl;
    }   
};

int main(int argc, char *argv[]) {
    Widget Widgets[1];
    cout << endl;
    vector<Widget> Widget_vec(1);
    Widget_vec.reserve(12);
    cout << Widget_vec.capacity() << endl;
    return 0;
}
/*
Widget constructed!

Widget constructed!
12
*/

从以上代码清楚看出,对于内置数组他会实例了对象,同样地如果在声明容器时提供了容器大小,他也一样会对实例化对象。但对于reserve函数则不会实例化。这是因为reserve只是告诉了这个容器,里面可以存放对象的数量,并没有告诉你已经可以使用这个对象,这有利于让使用者确定需要创建他时才创建他,更加地高效。

3. 调用empty()而不是检查size() == 0

对于任何的容器调用empty总是消耗常数时间。但计算size可并不一定如此。

问题就在于,并不是每个容器都给了size这个变量用来存储对象数量。特别是那些为了提高区间连接函数的执行效率的容器,他们并不会提供size这种常量。

典型的就是list的splice函数衔接功能。

#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main(int argc, char *argv[]) {
    list<int> l1{
  1, 2, 3, 4, 5};
    list<int> l2{
  5, 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值