一、错误处理(Error Handling)
STL的设计原则是效率优先,安全次之。STL仅仅加入了很少的错误检查。通常使用的STL都是非安全版本,如果索引、迭代器或区间不合法,都将导致未定义行为。所以一定要小心、谨慎的使用STL。
1. 迭代器务必合法、有效。注意vectors、deques发生元素的安插、删除或内存重新分配时,迭代器可能因此失效。
2. end()、rend()返回的“逾尾(past-the-end)”迭代器不指向任何对象,不能对它们调用operator*或operator->。
3. 区间(range)务必合法。用以指向某个区间的两个迭代器必须指向同一容器,并且从第一个迭代器出发,必须可到达第二个迭代器。
4. 如果涉及的区间不只一个,后续各区间必须拥有“至少和第一区间一样多”的元素。
5. 覆盖(overwritten)动作中的目标区间必须拥有足够元素,否则就必须采用insert iterators。
二、异常处理(Exception Handling)
STL几乎不检查逻辑错误,所以逻辑问题几乎不会引发STL产生异常。c++ standard似乎只要求唯一一个函数调用必要时直接产生异常:vector和deque的at()成员函数。此外,c++ standard要求只产生一般的(标准的)异常,如bad_alloc或用户自定义行为产生的异常。
c++ 标准程序库就“异常处理问题”提供以下基本保证(前提是:析构函数不得抛出异常)。
1. 在面对异常时,保证不会发生resources leak,也不会与容器的恒常特性(container invariants)发生抵触。
2. 所有node-based容器(Lists、sets、multisets、maps、multimaps),如果node构造失败,容器保持不变。移除node的操作保证不会失败。
3. 对于关联式容器。插入多个元素,失败时无法完全恢复原状;插入单一元素,支持commit-or-rollback。所有erase操作,无论是针对单一元素还是多个元素,肯定会成功。
4. 对于lists,插入多个元素的操作也属于transaction-safe。事实上list的所有操作,除了remove()、remove_if()、merge()、sort()、unique()之外(对于这几个函数,也提供了有条件的保证),都是commit-or-rollback。所以,如果需要一个transaction-safe容器,首选list。
5. 所有array-based容器(vectors、deques),insert元素时如果失败,都不会做到完全恢复。push、pop在容器尾端执行,万一发生异常,这两个动作可以保证容器会恢复原状。不过,如果元素的型别能够保证copy构造函数和assignment操作符不抛出异常,则所有加诸于该种元素上的操作,都能保证commit-or-rollback。
如果需要具备“完全commit-or-rollback能力”的容器,应当使用list(但不要调用它的sort()和unique()),或使用任何关联式容器(但不要对它安插多个元素)。如果不使用node-based容器,但又希望获得“完全commit-or-rollback能力”,就需要自己动手为每一个关键操作提供一份wrapper。例如:
即便是这份insert wrapper,也不能保证完全安全,比如swap()针对关联性容器复制“比较准则(comparison criterion)”时发生异常。可见,要想完美处理异常十分不易。
《c++ 标准程序库》读书笔记
联系客服