《Exceptional C++ Style》读书笔记

第1条 vector的使用

 

访问vector的元素

这个知识点主要是讲用[]和at访问vector元素的区别。大致的意思是,如果[]的话,因为标准并没有要求它做范围检查,所以效率较高,但访问越界不会抛出异常;如果是用at的话,因为其内部有范围检查,所以越界时会抛出异常。

下面是VS2005中的代码,请参考:

    reference at(size_type _Pos)

        {   // subscriptmutable sequence with checking

        if (size() <= _Pos)

            _Xran();     // 抛出异常

        return (*(begin() + _Pos));

        }

 

    const_reference operator[](size_type _Pos) const

        {   // subscriptnonmutable sequence

 

 #if _HAS_ITERATOR_DEBUGGING    // 只有调试下才检查,在VC6中,连调试状态下也没有检查

        if (size() <= _Pos)

            {

            _DEBUG_ERROR("vectorsubscript out of range");

            _SCL_SECURE_OUT_OF_RANGE;

            }

 #endif /*_HAS_ITERATOR_DEBUGGING */

        _SCL_SECURE_VALIDATE_RANGE(_Pos< size());

 

        return (*(_Myfirst +_Pos));

        }

 

我个人比较喜欢用[],这样看起来更像数组。再说了,我们的目标不是要异常,而是应该让自己写的代码根本就不会应该出现访问越界的情况。抛出异常你的程序能继续跑下去吗,不行!你同样还得找到为什么会越界。所以嘛,此条款意义不大。

 

 

调整vector的大小

这个知识点讲解vector中的size,resize,capacity,reserve之间的区别

 

我平常一般只用到size,所以虽然我对后面的函数不太清楚,也没犯错误。但是,当有时我们需要效率的时候,可能就要用到后面的函数了,所以还是得弄明白。

size和resize针对元素来说的,size返回元素的个数,resize的行为得根据原来的元素个数和要求的元素个数决定,这儿有个注意的地方,resize的Type_Val参数仅仅影响新添加的元素,不会改变老的元素值。当原来的元素个数大于要求的元素个数时,元素个数会变少,即被截断,但是内存空间不会被释放,即capacity不变。当原来的元素个数等于要求的元素个数时,什么行为都没有。感觉用resize初始化vector挺方便的,比如讲100个元素都初始化为2。

reserve和capacity都是针对存储空间来说的,reserve保证至少可以存储多少个元素不用再分配存储空间,capacity返回实际分配的存储空间,有时,可以采用reserve来防止重分配以提高性能。

 

 

看看这段代码有什么风格问题:

for(vector<int>::iterator i = v.begin(); i< v.end(); i++)

{

    cout<<*i<<endl;

}

(1)尽量做到const正确性,所以该用const_iterator。平常这个风格我已有,但是有时做不到。

(2)尽量使用!=而不是<来比较两个迭代器。平常这个风格我已有。

(3)尽量用前缀形式的--和++。我比较喜欢用后缀形式的,看来得改了。

(4)避免无谓的重复求值。这个我也经常做不到,但是这样可以少定义一个临时变量,当不要求效率时,我喜欢用重复求值。

(5)尽量使用\n而不是endl。记住endl会刷新缓冲区,如果你不要求刷新的话,就用\n。

(6)尽量使用标准库中的copy()和for_each(),而不是自己手写循环。我也想,但是不太会用呀!

例 copy(v.begin(), v.end(),ostream_iterator<int>(cout,"\n"));

将代码精简后,大概是这样

template<class _InIt, class _OutIt, class _InOutItCat>

inline

    _OutIt _Copy_opt(_InIt _First, _InIt _Last, _OutIt _Dest)

    {  

    for (; _First != _Last; ++_Dest, ++_First)  // 同时移动源和目的迭代器

        *_Dest = *_First; // 将当前源迭代器指向对象的值赋值给当前目的迭代器指向的对象

    return (_Dest);

    }

 

ostream_iterator<_Ty, _Elem, _Traits>& operator=(const _Ty& _Val)

    {

    //insert value into output stream, followed by delimiter

    *_Myostr << _Val;

    if (_Mydelim != 0)

        *_Myostr << _Mydelim;

    return (*this);

    }

首先是ostream_iterator<int>(cout,"\n")构造了一个临时的迭代器对象,并且将cout和"\n"作为对象的成员保存起来。而copy呢,仅仅是将源迭代器和目标迭代器进行移动并依次赋值,了解了这些之后,我们就可以灵活使用copy算法和迭代器对象了。

 

 

第2条 字符串格式化的“动物乐园”之一:sprintf

格式化有如下四种标准方式

sprintf

_snprintf

std::stringstream

std::strstream

另外还有

boost::lexical_cast

 

然后,作者对sprintf进行了评价:

1.易用性与清晰性都不错(其实对我来说的话,这点很重要

2.效率最佳(我有好几次性能问题都是格式化引起的,当我可以保证不越界的情况下,我会毫不犹豫的用它

3.长度安全性(没有)

4.类型安全性(没有,写正确这是写代码的人必须完成的,我觉得没必要考虑,虽然确实非常可能写错,但是只要跑一次代码就可以发现

5.模板亲和性(没有,我好像从来没有遇到过需要模板实现的情况

 

 

第9条 导出限制之一:基础

 

C++标准支持两种不同的模板源代码组织方式,其中之一就是我们年复一年地使用着的包含模式,另一种相对较新鲜的模式是分离模式(通过export关键字实现,像普通函数那样分离为.h和.cpp)。

 

准则:记住,export并不能带来像普通函数那样真正的分离式编译。(即当模板定义改变后,所有用到它的源文件都得重新编译连接,而不是像普通函数那样,仅仅需要连接就可以了)

准则:记住,export只能隐藏依赖性,并不能消除依赖性。(即编译器仍然必须知道模板的定义)

我们可以说依赖性在“用户阅读源代码”层面上被隐藏起来了。

 

小结:

本章我们考察了export特性背后的动机,以及为什么对于模板来说它并非是像普通函数那样的真正的分离式编译。许多人认为export即意味着可以不用发布模板库的实现源代码,或者构建速度会变得更快。然而实际上export根本没有保证会带来这两个优点。到目前为止C++社区的经验是:即便你使用了export,模板的源代码或者与它等价的东西仍然必须随库一同提供,同时构建速度可能并无甚改观,甚至更慢,鲜有更快的情况,从原则上来说这是因为依赖性虽然被掩盖起来了但依然不可否认地存在着,而只要存在依赖性,编译器就仍然需要像往常那样做一系列的工作(或许更多)。

 

参考MSDN中

The following topics are some of the known placeswhere the Visual C++ implementation of C++ does not agree with the C++standard. The section numbers refer to section numbers in the C++ standard.

 

Compiler Limits

 

10.3 (Paragraph 5) Covariant Return Types

 

14 export Keyword on a Template

 

14.6.2 Dependent Names

 

15.4 Function Exception Specifiers

 

16.3.2 The # Operator

 

Storage Location of Objects

 

由此可见,VS2005都是不支持export的,我们还有什么理由去使用它呢?再去研究它简直就是浪费时间了。

 

第10条 导出限制之二:相互影响,可用性问题以及准则

 

准则:如果你想编写可移植性的代码,那就别用export。

准则:(就目前而言)避免使用export(可能是最简单最有效的一句话了:)

 

......

认识到上面这些并非全部,你或许还会遇到一些其他的问题,而这些问题很可能超出了我们在通常的模板使用中积累的经验和认识。正如Spicer指出的:“很难单凭一些简单的准则就能够使我们免于麻烦。”要深刻认识到export某种程度上仍然还是一个实验性的特性,而且C++界目前尚没有机会来学习如何使用export,因此目前我们尚未能够列出一些让你可安全使用export的良好原则。在不久的将来这种状况或许会有改观。

 

条款15 访问权限的使用

 

准则:永远不要对语言搞破坏。例如,永远不要企图通过复制类定义再添加友元声明,或提供成员模板函数特化等途径来破坏封装性。

 

这个条款主要是讲有哪些办法可以访问私有成员,我不知道谁会干这种没意义的事情,虽然都是一些技巧,但没有实际意义,有兴趣的时候再来关注。

 

 

条款17 封装

 

要想知道为什么当初C++语言要加入保护数据的历史原因,以及为什么当初支持它的人现在也同意这是个糟糕的主义,可以参考Stroustrup的TheDisign and Evolution of C++。

准则:总是将所有数据成员放在私有区段。唯一的例外是C风格的struct,后者的意图并不在于封装什么东西,因而其所有成员可以是公用的。

 

而在实际当中,最糟糕的问题是,如果你一开始采用的就是示例17-2(a)中的做法,那么到了后期你可能根本就不被允许对其作出上述的改动(即改为示例17-2(b)那样的形式)。依赖于一个接口的用户越多,要想改变该接口就越是困难。

准则:接口是最需要在第一时间做对的事情。其他东西都可以在后期进行修正。如果你一开始就没有把接口做对的话,那么以后你可能就永远没有机会去改正它了。

 

我对这两个准则有异议,当类作者和使用者都是你一个人,并且该类使用的地方比较固定,接口不太好确定的时候,其实灵活的数据成员可能工作效率更高,难道get/set一下就成了好的接口了吗!当数据成员较多的时候,会烦死你的。另外,MFC中出现了大量的非私有成员,但是,我们好像也没有造成什么危害,反而用起来更加方便,当然,前提是你能保证实现不变。对于封装,我觉得还是不能一棍子打死,还得视情况而定。

 

 

 

第20条 内存中的容器之一:内存管理的层次

......

答案是存在着好几种可能的内存管理层面:

首先是操作系统内核提供了最为基础的内存分配服务。这个底层的分配策略及其特性可能根据操作系统平台的不同而有很大差异,而且这一层内存管理是最可能受到硬件相关的考虑所影响的。

 

编译器的默认运行时库也会建立起它自己的内存分配服务,比如C++中的operator new和C中的malloc,这一层面是建立在操作系统的本地分配服务基础上的,可能只是后者的一层薄薄的外衣,继承了其特性,也有可能通过先从后者那里买进大块的内存(chunk),然后再切割转手分配出去的费那个是来覆盖操作系统的本地内存分配服务,至于具体如何切割“买来的”大块内存就是由这一层运行时库自己自由选择的了。

 

而标准库容器和标准分配器进而又利用(也许会覆盖)运行时库提供的服务来实现它们自己的策略和优化。

 

最后,用户自定义的容器或用户自定义的分配器可能会进一步服用上面提到的任何一层服务(例如,如果可移植性并不那么重要的话,它们可能会想要直接访问本地分配服务),并按照它们自己的意愿来进行定制。

 

准则:弄清谁负责做什么:弄清你当前的平台和标准库使用的实际(且系统相关的)分配策略以及它们各自的职责是什么。

说实话,我还没完全搞清楚,得抽时间看看!

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
exceptional c++:47个c++工程难题、编程问题和解决方案(中文版)》讲述如何用标准c++进行企业级的软件开发,通过“问题/解答”的方式,启发读者思考,帮助了解隐藏在问题背后的设计思想,以及各种编程指导原则适用的场合。本书列出的条款涵盖了许多方面的主题,尤其对异常安全性、类和模块的合理设计,正确的代码优化,以及编写符合c++标准的可移植代码进行了深入的讨论。   《exceptional c++:47个c++工程难题、编程问题和解决方案(中文版)》适于有一定c++编程基础的读者阅读。 目录 《exceptional c++:47个c++工程难题、编程问题和解决方案(中文版)》 1 泛型程序设计与c++标准库 1 条款1:迭代器难度系数 1 条款2:大小写不敏感的字符串——之一 5 条款3:大小写不敏感的字符串——之二 9 条款4:可重用性最高的泛型容器——之一 12 条款5:可重用性最高的泛型容器——之二 13 条款6:临时对象 22 条款7:标准库的使用(或者,再论临时对象) 28 2 异常安全性相关的问题与技术 31 条款8:编写异常安全的代码——之一 32 条款9:编写异常安全的代码——之二 37 条款10:编写异常安全的代码——之三 40 条款11:编写异常安全的代码——之四 47 条款12:编写异常安全的代码——之五 50 条款13:编写异常安全的代码——之六 56 条款14:编写异常安全的代码——之七 62 条款15:编写异常安全的代码——之八 65 条款16:编写异常安全的代码——之九 68 条款17:编写异常安全的代码——之十 73 条款18:代码的复杂性——之一 75 条款19:代码的复杂性——之二 79 3 类的设计与继承 85 条款20:类的编写技巧 85 条款21:虚函数的重载 93 条款22:类之间的关系——之一 99 条款23:类之间的关系——之二 103 条款24:继承的使用和滥用 110 条款25:面向对象程序设计 121 4 编译器防火墙和pimpl惯用法 123 条款26:将编译期依赖性降到最低——之一 123 条款27:将编译期依赖性降到最低——之二 127 条款28:将编译期依赖性降到最低——之三 132 条款29:编译防火墙 135 条款30:fast pimpl惯用法 138 5 名字查找、名字空间和接口规则 148 条款31:名字查找与接口规则——之一 148 条款32:名字查找与接口规则——之二 152 条款33:名字查找和接口规则——之三 162 条款34:名字查找与接口规则——之四 167 6 内存管理 176 条款35:内存管理——之一 176 条款36:内存管理——之二 179 条款37:auto_ptr 186 7 误区、陷阱以及错误的惯用法 201 条款38:对象标识 201 条款39:自动转换 204 条款40:对象的生存期——之一 206 条款41:对象的生存期——之二 209 8 其他主题 219 条款42:变量的初始化 219 条款43:正确使用const 222 条款44:类型转换 231 条款45:bool 238 条款46:转调函数 242 条款47:控制流程 244 后记 254 参考书目 256

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值