The C++ standard library(侯捷/孟岩 译) 04--STL简述二

5.5 迭代器之配接器

C++标准程序库提供了多个预先定义的特殊迭代器,即迭代器配接器。下面简述三种迭代器配接器(详见page264)。

5.5.1 insert iterator

插入型迭代器使算法insert方式而非overwrite方式运作,可解决“目标空间不足”的问题。
insert iterator对内部接口做了新定义:
若对某元素进行assign,会引发“对元素所属群集的insert操作”,insert的位置须视三种不同的insert iterator而定:

单步前进(step forward):不会造成任何动静。eg:
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    list coll1;

    // insert elements from 1 to 9 into the first collection
    for (int i = 1; i <= 9; ++i)
    {
        coll1.push_back(i);
    }

    // copy the elements of coll1 into coll2 by appending them
    vector coll2;
    copy (coll1.begin(), coll1.end(),   // source
            back_inserter(coll2));  // destination

    // copy the elements of coll1 into coll3 by inserting them at the
    // front -- reverses the order of the elements
    deque coll3;
    copy (coll1.begin(), coll1.end(),   // sourcce
            front_inserter(coll3)); // destination

    // copy the elements of coll1 into coll4
    // - only inserter that works for associative collections
    set coll4;
    copy(coll1.begin(), coll1.end(),    // source
            inserter(coll4,coll4.begin())); // destination
}

上例用了三种预定义的insert iterator:
1. back inserter(安插于容器末端)
    back inserter的内部调用push_back()。(当然只有支持push_back()的容器才能用back_inserter())
    完成语句copy(coll1.begin(), coll1.end(), back_inserter(coll2));后coll1的所有元素会附加到coll2中。
2.front inserter(安插于容器首部)
    front inserter内部调用push_front()。
3. inserter(一般性inserter)
    将元素插入“初始化时接受的第二参数”所指位置的前方。
    内部调用insert()并以新值和新位置作为参数。

p106-t5-1.png

所有STL容器都提供insert()成员函数,故inserter是唯一可用到关联式容器上的insert iterator。
对关联式容器进行insert时,所给的插入位置只是一个提示,帮助它从何开始查找正确的插入位置,若提示不正确,效率上会比“没有提示”更糟糕。

5.5.2 stream iterator

用于“读写”stream的迭代器,是的来自键盘的输入像个群集,能从中读取内容。同理输出结果导出到文件或屏幕。

// stl/ioiter1.cpp

#include 
#include 
#include  // 使用iterator要包含此文件
#include 
#include 
using namespace std;

int main()
{
    vector col1;

    /* read all words from the standard input
     * -source: all strings until end-of-file (or enter)
     * -destination: col1 (inserting)
     */
    copy (istream_iterator (cin),   // start of source
            istream_iterator (),    // end of source
            back_inserter(col1));   // destination

    // sort elements
    sort (col1.begin(), col1.end());

    /* print all elements without duplicates
     * - source: col1
     * - destination: standard output (with newline between elements)
     */
    unique_copy (col1.begin(), col1.end(),  // source
            ostream_iterator (cout,"\n"));  // destination
}

istream_iterator(cin)会产生一个
  可从standard input stream(cin)读取数据的stream iterator。
    其中的template参数string表该stream iterator专门读取string型别元素。
    这些元素通过operator>>读取进来,每当企图处理下一个元素时,
        istream iterator会将这种企图化作 cin>>string;来执行。
note:针对string而执行的input操作符通常读取以空白分隔的文字(详见page492)。

istream_iterator()调用istream iterator的default ctor,
    产生一个代表“流结束符号(end-of-stream)”的迭代器,其意义:不能再从中读取任何东西。
  • copy()函数中,只要不断逐一前进的第一参数不等于第二参数,copy操作就会继续。
  • unique_copy (col1.begin(), col1.end(),ostream_iterator (cout,"\n")); 将所有元素拷贝到cout端并消除相邻重复值。
    其中ostream_iterator (cout,"\n")产生一个output stream iterator,通过operator<<向cout写入string。cout的第二参数(可有可无)用来作为元素之间的分隔符。
5.5.3 reverse iterator(逆向迭代器)(page109)

以逆向方式进行所有操作,将increment/decrement运算转换为decrement/increment运算。所有容器可用成员函数rbegin()和rend()产生reverse iterator,eg:

// stl/riter1.cpp

#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    vector col1;

    // insert elements from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        col1.push_back(i);
    }

    // print all element in reverse order
    copy (col1.rbegin(), col1.rend(), ostream_iterator(cout," "));

    cout << endl;
}
  • rbegin(),指向的群集的结尾位置(即最后元素的下一位置),故*col1.rbegin()返回最后一个元素的值。
  • rend(),“对群集元素逆向遍历”的终点,即反方向的“逾尾”位置,指的是容器内的第一个元素的前一个位置。
    note: *end()和 *rend()是未定义的。

5.6 更易型算法(manipulating algorithm)

manipulating algorithm指 会删除或重排或修改元素的算法。

5.6.1 removing(移除元素)
  • remove()从某区间删除元素。
// remove()删除容器中元素
// stl/remove1.cpp

#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    list col1;

    // insert elements from 6 to 1 and 1 to 6
    for (int i = 1; i <= 6; ++i)
    {
        col1.push_front(i);
        col1.push_back(i);
    }

    // print all elements of the collection
    cout << "pre: ";
    copy (col1.begin(), col1.end(), ostream_iterator(cout, " "));
    cout << endl;

    // remove all elements with value 3
    remove (col1.begin(), col1.end(), 3);

    // print all elements of the collection
    cout << "post: ";
    copy (col1.begin(), col1.end(), ostream_iterator(cout, " "));
    cout << endl;
}

但输出结果:


remove1.png

事实上,remove()返回了一个新终点,可用该终点获得新区间、缩减后的容器大小或获得被删除元素的个数。eg:

// stl/remove2.cpp

#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    list col1;

    // insert elements from 6 to 1 and 1 to 6
    for (int i=1; i <= 6; ++i)
    {
        col1.push_front(i);
        col1.push_back(i);
    }

    // print all elements of the collection
    copy (col1.begin(), col1.end(), ostream_iterator(cout," "));
    cout << endl;

    // remove all elements with value 3
    // - retain new end
    list::iterator end = remove(col1.begin(), col1.end(), 3);

    // print resulting elements of the collection
    copy (col1.begin(), end, ostream_iterator(cout," "));
    cout << endl;

    // print number of resulting elements
    cout << "number of removed elements: "
        << distance (end, col1.end()) << endl;
 
    // remove "removed" elements
    col1.erase (end, col1.end());

    // print all elements of the modefied collection
    copy (col1.begin(), col1.end(), ostream_iterator(cout," "));
    cout << endl;
}
  • distance()返回两个迭代器之间的距离。(若迭代器是随机存取迭代器,则可用operator-直接计算)
  • erase()可删除“参数所指区间内”全部元素。

但通常无必要删除“已移除”的元素,只需将逻辑终点取代容器实际终点即可。

若需以单一语句删除元素,可以如此:col1.erase(remove(col1.begin(), col1.end(), 3), col1.end());
为什么算法不自己调用erase()?:

这个问题点出来STL为了获取灵活性而付出的代价。
    透过“以迭代器为接口”,STL将数据结构和算法分离。
然而,迭代器只是“容器中某个位置”的抽象概念,
一般而言,迭代器对自己所属容器一无所知。
任何“以迭代器访问容器元素”的算法,都无法通过迭代器调用容器类别所提供的任何成员函数。

如此设计会导致:算法的操作对象不一定得是“容器内全部元素”形成的区间,
    可以是元素的子集,甚至算法可运作于一个“未提供成员函数erase()”的容器(如array)。
    故为使算法具有最大弹性,不要求“迭代器必须了解容器细节”是合理的。
5.6.2 manipulating algorithm and associative container(page115)

manipulating algorithm不能将关联式容器作为操作对象,因为:将manipulating algorithm作用于容器上会改变某位置的值,进而破坏关联式容器已序的特性,就推翻了关联式容器的基本原则(容器中元素总是据某排序准则自动排序)。
为了保证该准则,关联式容器的所有迭代器均被声明为指向常量。

从关联式容器中删除元素方法:调用成员函数。
每一中关联式容器都提供了移除元素的成员函数:

// stl/remove3.cpp

#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    set col1;

    // insert element from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        col1.insert(i);
    }

    // print all elements of the collection
    copy (col1.begin(), col1.end(), ostream_iterator(cout, " "));
    cout << endl;

    /* remove all elements with value 3
    * - algorithm remove() does not work
    * - instead member function erase() works
    */
    int num = col1.erase(3);

    // print number of removed elements
    cout << "number of removed elements: " << num << endl;

    // print all elements of the modified collection
    copy (col1.begin(), col1.end(), ostream_iterator(cout," "));
    cout << endl;
}

note:容器类提供了多个不同的erase()成员函数,其中一种形式是“以待删除的元素值”为唯一参数,返回被删除的元素个数。

5.6.3 算法VS成员函数(page116)

就算符合种种条件可以使用某算法(指algorithm中的函数),但未必是好的。容器本身可能提供功能类似但性能更好的成员函数。eg:对list元素调用remove()。

算法本身不知道工作于list上,故像在任何容器中一样做着相似的工作:
      改变元素值,从而重新排列元素。
    若移除第一个元素,则后面所有元素就会分别被赋值给各自的前一个元素。
    这就违反了list的主要优点:通过修改link而非value来插入/移动/移除元素。

为了避免这个问题,list针对所有manipulating algorithm提供了一些对应的成员函数。
  故,若使用list,则应使用这些成员函数。
    且成员函数真的移除了“被移除”的元素(且不是某种移动而已)eg:
// stl/remove4.cpp
#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    list col1;

    // insert elements from 6 to 1 and 1 to 6
    for (int i = 1; i <= 6; ++i)
    {
        col1.push_front(i);
        col1.push_back(i);
    }

    // remove all elements with value 3
    // - poor performance
    col1.erase(remove(col1.begin(), col1.end(), 3), col1.end());

    // remove all elements with value 4
    // - good performance
    col1.remove(4);
}

如果追求高效率,则应选择成员函数。(前提是知道某容器存在效率较好的成员函数)。
如果使用成员函数,一旦换用另一种容器,则不得不改动程序代码。

5.7 User-Defined Generic Functions(page118)

STL是一个可扩展的框架。故可自定义算法函数处理群集元素。当然这些操作函数也可以是泛型的(generic)。
为了在这些操作中声明有效的迭代器,须使用容器提供的型别,因为每种容器有各自的迭代器。
为了方便写出泛型函数,每种容器都提供了一些内部的型别定义。eg:

// 定义了一个泛型函数,可打印一个字符串(也可不指定),然后打印容器全部元素
// stl/print.hpp

#include 

/* PRINT_ELEMENTS()
 * - prints optional C-string optcstr followed by
 * - all elements of the collection col1
 * - separated by spaces
 */
template 
inline void PRINT_ELEMENTS (const T& col1, const char* optcstr = "")
{
    typename T::const_iterator pos;

    std::cout << optcstr;
    for (pos = col1.begin(); pos != col1.end(); ++pos)
    {
        std::cout << *pos << ' ';
    }
    std::cout << std::endl;
}

其中pos被声明为“传入容器型别”内的迭代器型别,typename用以表明const_iterator是型别T所定义的一个型别而非一个型别为T的值。

5.8 一函数作为算法函数的参数(page119)

一些算法函数可接受自定义的辅助性函数,以提高灵活性。

5.8.1函数作为算法函数的参数

eg:for_each(),针对区间内每个元素,调用一个用户自定义函数。

// stl/foreach1.cpp

#include 
#include 
#include 
#include 
using namespace std;

// function that prints the passed argument
void print (int elem)
{
    cout << elem << ' ';
}

int main()
{
    vector col1;

    // insert elements from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        col1.push_back(i);
    }

    // print all elements
    for_each (col1.begin(), col1.end(), print);
    cout << endl;
}

输出:
foreach1.png

算法函数对这些辅助函数的态度:有的视为是必要的,有的视为可有可无。可利用这些辅助函数来指定查找准则、排序规则或定义某种操作,以便将某种容器内的元素转换到另一个容器。eg:

// stl/transform1.cpp

#include 
#include 
#include 
#include 
#include 
#include "print.hpp"

int square (int value)
{
    return value*value;
}

int main()
{
    std::set coll1;
    std::vector coll2;

    // insert elements from 1 to 9 into coll1
    for (int i = 1; i <= 9; ++i)
    {
        coll1.insert(i);
    }
    PRINT_ELEMENTS(coll1,"initialized: ");

    // transform each element from coll1 to coll2
    // - square transformed values
    std::transform (coll1.begin(), coll1.end(), 
                    std::back_inserter(coll2),square);

    PRINT_ELEMENTS(coll2,"squared: ");
}

输出:
transform1.png
5.8.2 predicate(谓词/判别式)(page121)

所谓predicates,即返回bool的函数。通常用于指定排序或查找准则。视情况可能有一或两个操作数。
note:并非任何返回bool的一元或二元函数就是合法的predicate,STL要求面对相同的值,predicates必须得出相同的结果,这个要求将“被调用时会改变自己内部状态”的函数清除出场。(详见page302)

  • Unary Predicate(一元谓词/一元判别式)
    Unary Predicate会检查唯一参数的某特性,eg:
// 用来搜寻第一个质数
// stl/prime1.cpp

#include 
#include 
#include 
#include   // for abs()
#include 
using namespace std;

// predicate, which returns whether an integer is a prime number
bool isPrime (int number)
{
    // ignore negative sign
    number = abs(number);

    // 0 and 1 are prime numbers
    if (number == 0 || number == 1)
    {
        return true;
    }

    // find divisor that divides without a reminder
    int divisor;
    for (divisor = number/2; number%divisor != 0; --divisor)
    {
        ;
    }

    // if no divisor greater than 1 is found,it is a prime number
    return divisor == 1;
}

int main()
{
    list col1;

    // insert elements from 24 to 30
    for (int i = 24; i <= 30; ++i)
    {
        col1.push_back(i);
    }

    // search for prime number
    list::iterator pos;
    pos = find_if (col1.begin(), col1.end(), isPrime);

    if (pos != col1.end())  // found
    {
        cout << *pos << " is first prime number found " << endl;
    }
    else    // not found
    {
        cout << "no prime number found " << endl;
    }
}

输出:

prime1.png

find_if()在给定区间内寻找使“被传入的一元谓词”运算结果为true的第一个元素。如果没有一个元素使得一元谓词结果为true,find_if()就返回区间的终点即第二个参数。

  • Binary Preidcate(二元谓词/二元判别式)(page123)
    典型用途:比较两参数的特定属性。(eg定义operator<或自定义排序规则)eg:
// 根据每个人的姓名,对一组元素进行排序
// stl/sort1.cpp

#include 
#include 
#include 
#include 
#include 
using namespace std;

class Person
{
public:
    Person(const char* s1, const char* s2):ftname(s1),ltname(s2){}
    string firstname() const
    {
        return string(ftname);
    }
    string lastname() const
    {
        return string(ltname);
    }
    //...
private:
    const char* ftname;
    const char* ltname;
};

/* binary function predicate:
 * - returns whether a person is less than another person
 */
bool personSortCriterion (const Person& p1, const Person& p2)
{
    /* a person is less than another person
     * - if last name is less
     * - if the last name is equal and the first name is less
     */
    return p1.lastname() < p2.lastname() ||
        ( !(p2.lastname() < p1.lastname()) &&
          p1.firstname() < p2.firstname());
}

int main()
{
    deque col1;

    col1.push_back(Person("Erii","Tom"));
    col1.push_back(Person("Lise","Chris"));
    // ...
    sort (col1.begin(), col1.end(), personSortCriterion);
    // ...
    
    for (auto iter = col1.begin(); iter != col1.end(); ++iter)
    {
        cout << "firstname: " << iter->firstname() << "---lastname: " << iter->lastname() << endl;
    }
}

输出:
sort1.png

注:也可用仿函数(functor/function object)来实现一个排序准则。如此的优点:实现的准则将是一个型别(type),可用于“声明一个set,以某型别为排序准则”之类的事。

5.9 仿函数(functor/function object)

传递给算法的“函数型参数(functional argument)”并不一定要是参数,可以是具有函数行为的对象,即function object/functor。函数行为指可以“使用小括号传递参数以此调用某个东西”。eg:

// 是page119例子的一个仿函数版本,行为和使用与一般函数完全相同

// stl/foreach2.cpp

#include 
#include 
#include 
using namespace std;

// simple function object that prints the passed argument
class PrintInt
{
public:
    void operator() (int elem) const 
    {
        cout << elem << " ";
    }
};

int main()
{
    vector col1;

    // insert elements from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        col1.push_back(i);
    }

    // print all elements
    for_each (col1.begin(), col1.end(), PrintInt());
    cout << endl;
}

表达式for_each(col1.begin(), col1.end(), PrintInt())中的PrintInt()产生此类的一个临时对象,作为for_each()的参数。for_each大致code:

namespace std
{
    template 
    Operation for_each (Iterator act, Iterator end, Operation op)
    {
        while (act != end)  // as long as not reached the end
        {
            op(*act);   // call op() for actual element
            ++act;  // move iterator to the next element
        }
        return op;
    }
}
5.9.1 仿函数优点(page127)

相比较于一般函数,仿函数更复杂,但优点:

1. 仿函数是“smart functions”(智能型函数)
    “行为类似指针”的对象,称为smart pointers,同理smart functions。
    仿函数可拥有成员函数和成员变量,即仿函数拥有状态。
    事实上,同一时间内,由某仿函数代表的单一函数可能有不同的状态(一般函数中不存此情况)。
    另,可在执行期(指被调用之前)初始化它们。

2. 仿函数各有其型别
    由仿函数定义的每个函数行为都有各自的型别。
        故可将函数行为作为template参数运用。

3. 仿函数速度通常比一般函数快
    就template而言,很多细节在编译期就确定了,故可能进行更好的优化。
      故传入一个仿函数可能获得更好的性能。
  • functor example(page127)
    假设要对群集中各元素加上一个固定值。若编译期知晓该固定值,则可用一般函数:
void add10(int& elem)
{
    elem += 10;
}
void f1()
{
    vector col1;
    // ...
    for_each (col1.begin(), col1.end(), add10);
}
  • 假设要对群集中各元素加上多个不同的固定值且编译期已知晓,则可使用template:
template 
void add(int& elem)
{
    elem += theValue;
}
void f1()
{
    vector col1;
    // ...
    for_each (col1.begin(), col1.end(), add<10>);
}

使用仿函数,对象可以有自己的状态,可被正确的初始化。eg:

// stl/add1.cpp

#include 
#include 
#include 
#include "print.hpp"
using namespace std;

// function object that adds the value with which it is initialized
class AddValue
{
    private:
        int theValue;   // the Value to add
    public:
        // constructor initializes the values to add
        AddValue(int v) : theValue(v){}

        // the "function call" for the element adds the value
        void operator() (int& elem) const
        {
            elem += theValue;
        }
};

int main()
{
    list col1;

    // insert elements from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        col1.push_back(i);
    }

    PRINT_ELEMENTS(col1, "initialized: ");
    
    // add value 10 to each element 
    for_each (col1.begin(), col1.end(), AddValue(10));

    PRINT_ELEMENTS(col1, "after adding 10: ");

    // add value of first element to each element
    for_each (col1.begin(), col1.end(), AddValue(*col1.begin()));

    PRINT_ELEMENTS(col1, "after adding first element: ");
}

输出:
add1.png

运用此法,前述“一个函数两个状态”问题可用“两个不同仿函数”解决。eg:声明两个仿函数让后各自运用。

AddValue addx(x);  // function object that adds value x
AddValue addx(y);  // function object that adds value y

for_each (col1.begin(), col1.end(), addx);  // add value x to each element
// ...
for_each (col1.begin(), col1.end(), addx);  // add value x to each element
// ...
for_each (col1.begin(), col1.end(), addx);  // add value x to each element

同理可提供一些成员函数,在仿函数生命期间查改对象状态。
注:C++标准程序库未限制算法“对容器元素”调用仿函数的次数,故可能导致同一仿函数的若干副本传给元素。若将仿函数作为predicate用会导致很多麻烦。(详见p302)

5.9.2 预定义的仿函数(page131)

C++标准程序库包含了很多仿函数。eg:

operator<对应的排序准则仿函数是less<>,
    故set col1; 会被扩展为set > col1;  // sort elements with <
如此,若要方向排序,则set > col1;  // sort elements with >

类似,将群集中元素取反:
transform (col1.begin(), col1.end(),   // source
        col1.begin(),                  // destination
        negate() );              // operation

其中negate() 是由预定义的template class negate生成的一个仿函数。
同理,可对群集内元素取平方:

// process the square of all elements
transform (col1.begin(), col1.end(),    // first source
            col1.begin(),               // second source
            col1.begin(),               // destination
            multiplies() );        // operation

使用一些特殊的函数配接器(function adaptors),可将预定义的仿函数和其它数值组合在一起,或使用特殊情况。eg:

 // stl/fo1.cpp

#include 
#include 
#include 
#include 
#include "print.hpp"
using namespace std;

int main()
{
    set > coll1;
    deque coll2;

    // insert elements from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        coll1.insert(i);
    }

    PRINT_ELEMENTS(coll1, "initialized: ");

    // transform all element into coll2 by multiplying 10
    transform (coll1.begin(), coll1.end(),  // source
            back_inserter(coll2),           // destination
            bind2nd(multiplies(), 10));// operation

    PRINT_ELEMENTS(coll2, "transformed: ");

    // replace value equal to 70 with 42
    replace_if (coll2.begin(), coll2.end(), // range
            bind2nd(equal_to(), 70),   // replace criterion
            42);                            // new value
    
    PRINT_ELEMENTS(coll2, "replaced: ");

    // remove all elements with value less than 50
    coll2.erase(remove_if(coll2.begin(), coll2.end(),   // range
                bind2nd(less(), 50)),  // remove criterion
            coll2.end());   

    PRINT_ELEMENTS(coll2, "removed: ");
}

输出:

fo1.png

note:所有仿函数通常声明为inline。
另有些仿函数可调用群集内每个元素的成员函数:

for_each(col1.begin(), col1.end(),    // range
                  mem_fun_ref(&Person::save) );  // operation

仿函数mem_fun_ref()用于调用其作用元素的某个成员函数。

5.10 容器内的元素(page134)

容器以特定方式操作其元素故容器元素须满足特定条件。

5.10.1 容器元素的条件

STL的容器、迭代器、算法都是template,故无论是预定义或自定义型别都可操作。但不同操作须满足不同条件,STL容器元素须满足三个基本条件:

1. 须通过copy构造函数进行复制。
    所有容器都会在内部生成一个元素副本并返回该暂时性副本,故copy构造函数会被频繁调用。
    若对象拷贝的功耗高,则用reference语意使用容器以避免拷贝。(详见page222)。
2. 须通过assignment操作符完成赋值。
    容器和算法都用assignment才能以新元素改写旧元素。
3. 须通过析构函数完成销毁动作。
    当容器元素被移除,其容器中的副本将被销毁,
        故析构函数不能为private。
    另,C++惯例,析构函数不允许抛出异常。

上述三个条件对任何class都是隐式成立的。(若没定义上述操作的特殊版本,也没定义“可能破坏这些操作健全性”的成员,则自然满足上述三条件)。
还应满足以下条件:

  • 对sequence container,元素default构造函数须可用。
  • 对某些操作须定义operator==以测试相等性。如有查询需求。
  • associate container中,元素须定义排序准则。缺省是operator<(通过仿函数less<>被调用)。
5.10.2 Value语意 VS reference语意(page135)

STL只支持value语意而不支持reference语意。优缺点:

优点:
1. 元素的拷贝简单。
2. 使用reference易出错,
        须确保reference所指对象存在,并小心偶现的循环引用。

缺点:
3. “拷贝元素”可能性能低下;有时无法拷贝。
4. 无法在多个不同容器中管理同一份对象。

想要获得适用于STL容器的reference语意,须自定义合适的智能指针(eg使用“引用计数”智能型指针实现STL容器的reference语意)。(详见page222)

5.11 STL内部错误处理和异常处理

逻辑性错误和运行环境所致的运行时错误都能被异常机制处理。

5.11.1 错误处理(page137)

STL设计原则是效率优先,安全次之,而错误检查耗时,故几乎没有。
大部分决定不加入错误检查,原因:

  • 错误检查耗时会降低效率,而程序的总体目标是 速度
  • 若认为安全重于效率,则可加一层包装或使用STL特殊版本。但加入错误检验后就无法消除其以获得更高效率,反过来则可以。
    故错误检验是可获得的但不是STL内在条件。

C++标准程序指出,对STL的任何运用,若违反规则会导致未定义行为(eg 索引、迭代器或区间不合法)。
具体而言,使用STL须满足以下要求:

1. 迭代器合法且有效。
        迭代器可能某些操作会无效(vector/deque的插入删除、内存重配)
2. 迭代器指向“逾尾(past-the-end)”位置(end()和rend()),但不指向任何对象。
        故不能对其调用oeprator*或operator->。
3. 区间须合法:
        用以指出某钱的前后迭代器须指向同一容器。
        从第一个迭代器出发可到达第二个迭代器所指位置。
4. 若区间不只一个,第二区间及后继区间拥有的元素须大于等于第一区间的元素数量。
5. overwrite操作的目标区间须有足够元素,否则就须用inert iterator。

eg,错误示例

// stl/iterbug1.cpp

#include 
#include 
#include 
using namespace std;

int main()
{
    vector coll1;  // empty collection
    vector coll2;  // empty collection

    /* RUNTIME ERROR:
     * - beginning is behind the end of range
     */
    vector::iterator pos = coll1.begin();
    reverse (++pos, coll1.end());
    
    // insert elements from 1 to 9 into coll2
    for (int i = 1; i <= 9; ++i)
    {
        coll2.push_back(i);
    }

    /* RUNTIME ERROR:
     * - overwriting nonexisting elements
     */
    copy (coll2.begin(), coll2.end(), coll1.begin());

    /* RUNTIME ERROR:
     * - collections mistaken
     * - begin() and end() mistaken
     */
    copy (coll1.begin(), coll2.end(), coll1.end());
}
5.11.2 异常处理(page139)

C++标准程序库如今有如下保证(前提是析构函数不抛出异常):(异常发生时各种容器操作,详见page248)

1. 对所有“以节点为基础(node-based)实现”的容器(list/set/multiset/map/multimap etc):
      若节点构造失败,容器保持不变不收影响(即支持事务回滚commit-or-rollback)。

2. 对"以array为基础(array-based)实现"的容器(vector/deque etc):
        若插入元素失败,若想要完全回复,则须在插入操作之前拷贝插入点之后所有的元素。
        push和pop作用与容器末端,不需拷贝元素即可回复原状。

你可能感兴趣的:(The C++ standard library(侯捷/孟岩 译) 04--STL简述二)