Effective STL(摘录-01)

在另一个场景里,这不再是风格问题。为了避免潜在的解析含糊(我将提供给你细节),你被要求在依赖形式类型参数
的类型名字之前使用typename。这样的类型被称为依赖类型,一个例子将帮助阐明我所说的。假设你想为函数写一个模
板,给定一个STL容器,返回容器中的最后一个元素是否大于第一个元素。这是一种方法:
template<typename C>
bool lastGreaterThanFirst(const C& container)
{
        if (container.empty()) return false;
        typename C::const_iterator begin(container, begin());
        typename C::const_ierator end(container.end());
        return *--end > *begin;
}
在这个例子里,局部变量begin和end的类型是C::const_iterator。const_iterator是依赖形式类型参数C的一种类型。因为C::
const_iterator是一种依赖类型,你被要求在它之前放上typename这个词。(一些编译器错误地接受没有typename的代
码,但这样的代码不可移植。)

 

 

 

 

 

 

 

 

 

这里再写一遍:
list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());
打起精神,这声明了一个函数data,它的返回类型是list<int>。这个函数data带有两个参数:
●     第一个参数叫做dataFile。它的类型是istream_iterator<int>。dataFile左右的括号是多余的而且被忽略。
●     第二个参数没有名字。它的类型是指向一个没有参数而且返回istream_iterator<int>的函数的指针。
奇怪吗?但这符合C++里的一条通用规则——几乎任何东西都可能被分析成函数声明。如果你用C++编程有
一段时间了,你应该会遇到另一个这条规则的表象。有多少次你会看见这个错误?
class Widget {...};     // 假设Widget有默认构造函数
Widget w();             // 嗯哦……
这并没有声明一个叫做w的Widget,它声明了一个叫作w的没有参数且返回Widget的函数。学会识别这个失言
(faux pas)是成为C++程序员的一个真正的通过仪式。

 

所有这些都很有趣(以它自己的扭曲方式),但它没有帮我们说出我们想要说的,也就是应该用一个文件的
file:///D|/Documents%20and%20Settings/Fingster/My%20Documents/Effective%20STL/item_06.html (2 of 3)2005-4-26 15:15:15条款6:警惕C++最令人恼怒的解析
内容来初始化一个list<int>对象。现在我们知道了我们必须战胜的解析,那就很容易表示了。用括号包围一个
实参的声明是不合法的,但用括号包围一个函数调用的观点是合法的,所以通过增加一对括号,我们强迫编
译器以我们的方式看事情:
list<int> data((istream_iterator<int>(dataFile)),       // 注意在list构造函数
                        istream_iterator<int>());       // 的第一个实参左右的
                                                // 新括号
这是可能的声明数据方法,给予istream_iterators的实用性和区间构造函数(再次,参见条款5),值得知道它
是怎样完成的。
不幸的是,目前并非所有编译器都知道它。在我测试的几种中,几乎一半拒绝接受数据的声明,除非它错误
地接受没有附加括号形式的声明!为了安慰这样的编译器,你可以滚你眼睛并且使用我辛辛苦苦解释的错误
的数据声明,但那将不可移植而且目光短浅。毕竟,目前编译器存在的分析错误肯定会在将来被修正,是
吧?(肯定的!)
一个更好的解决办法是在数据声明中从时髦地使用匿名istream_iterator对象后退一步,仅仅给那些迭代器名
字。以下代码到哪里都能工作:
ifstream dataFile("ints.dat");
istream_iterator<int> dataBegin(dataFile);
istream_iterator<int> dataEnd;
list<int> data(dataBegin, dataEnd);
命名迭代器对象的使用和普通的STL编程风格相反,但是你得判断这种方法对编译器和必须使用编译器的人
都模棱两可的代码是一个值得付出的代价。

 

 struct DeleteObject {                           // 删除这里的
                                                // 模板化和基类
        template<typename T>                    // 模板化加在这里
        void operator()(const T* ptr) const
        {
                delete ptr;
        }
}

 

 

 

 

 

 

为了避免这个问题,我们必须保证在调用erase之前就得到了c中下一元素的迭代器。最容易的方法是当我们调
用时在i上使用后置递增:
AssocContainer<int> c;
...
for (AssocContainer<int>::iterator i = c.begin();       // for循环的第三部分
        i != c.end();                           // 是空的;i现在在下面
        /*nothing*/ ){                          // 自增
        if (badValue(*i)) c.erase(i++);         // 对于坏的值,把当前的
        else ++i;                                       // i传给erase,然后
}                                               // 作为副作用增加i;
                                                // 对于好的值,
                                                // 只增加i
这种调用erase的解决方法可以工作,因为表达式i++的值是i的旧值,但作为副作用,i增加了。因此,我们把i
的旧值(没增加的)传给erase,但在erase开始执行前i已经自增了。那正好是我们想要的。正如我所说的,代
file:///D|/Documents%20and%20Settings/Fingster/My%20Documents/Effective%20STL/item_09.html (3 of 6)2005-4-26 15:15:34条款9:在删除选项中仔细选择
码很简单,只不过不是大多数程序员在第一次尝试时想到的。
现在让我们进一步修改该问题。不仅删除badValue返回真的每个元素,而且每当一个元素被删掉时,我们也
想把一条消息写到日志文件中。
对于关联容器,这说多容易就有多容易,因为只需要对我们刚才开发的循环做一个微不足道的修改就行了:
ofstream logFile;                                       // 要写入的日志文件
AssocContainer<int> c;
...
for (AssocContainer<int>::iterator i = c.begin();       // 循环条件和前面一样
        i !=c.end();){
        if (badValue(*i)){
                logFile << "Erasing " << *i <<'/n';     // 写日志文件
                c.erase(i++);                   // 删除元素
        }
        else ++i;
}
现在是vector、string和deque给我们带来麻烦。我们不能再使用erase-remove惯用法,因为没有办法让erase或
remove写日志文件。而且,我们不能使用刚刚为关联容器开发的循环,因为它为vector、string和deque产生未
定义的行为!要记得对于那样的容器,调用erase不仅使所有指向被删元素的迭代器失效,也使被删元素之后
的所有迭代器失效。在我们的情况里,那包括所有i之后的迭代器。我们写i++,++i或你能想起的其它任何东
西都没有用,因为没有能导致迭代器有效的。
我们必须对vector、string和deque采用不同的战略。特别是,我们必须利用erase的返回值。那个返回值正是我
们需要的:一旦删除完成,它就是指向紧接在被删元素之后的元素的有效迭代器。换句话说,我们这么写:

 

for (SeqContainer<int>::iterator i = c.begin();
        i != c.end();){
        if (badValue(*i)){
                logFile << "Erasing " << *i << '/n';
                i = c.erase(i);                 // 通过把erase的返回值
        }                                       // 赋给i来保持i有效
        else
                ++i;

}

 

 

 

这可以很好地工作,但只用于标准序列容器。由于论证一个可能的问题(条款5做了),标准关联容器的erase
的返回类型是void
[1]
。对于那些容器,你必须使用“后置递增你要传给erase的迭代器”技术。(顺便说说,
在为序列容器编码和为关联容器编码之间的这种差别是为什么写容器无关代码一般缺乏考虑的一个例子——
参见条款2。)

 

 

    在循环内做某些事情(除了删除对象之外):
如果容器是标准序列容器,写一个循环来遍历容器元素,每当调用erase时记得都用它的返回值更新你
的迭代器。
如果容器是标准关联容器,写一个循环来遍历容器元素,当你把迭代器传给erase时记得后置递增它。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值