posts - 13, comments - 4, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Exceptional C++ Style 读书笔记(一)

Posted on 2008-11-03 20:36 Batiliu 阅读(468) 评论(0)  编辑 收藏 引用 所属分类: 读书笔记

第一条 vector的使用

设计vector是用来代替内置数组的,因此其效率应该与内置数组一样,内置数组在下标索引(operator[])时是不进行越界检查的。如果你需要下标越界检查,可以使用at方法。

 

准则记住size/resize以及capacity/reserve之间的区别。
  • size告诉你容器中目前实际有多少个元素,而对应地,resize则会在容器的尾部添加或删除一些元素。这两个函数对list、vector、deque都适用,但对其他容器并不适用。
  • capacity则告诉你最少添加多少个元素才会导致容器重分配内存,而reserve在必要的时候总是会使容器的内部缓冲区扩充至一个更大的容量,以确保至少能满足你所指出的空间大小。这两个函数仅对vector适用。

 

 

准则

  • 记得使用const_iterator。
  • 尽量使用!=而不是<来比较两个iterator。
  • 养成默认情况下使用前缀形式的--和++的习惯,除非你的确需要用到原来的值。
  • 尽量服用标准库中已有的算法,例如for_each()。

 

 

第二条 第三条 字符串格式化

       sprintf     snprintf     stringstream     strstream     boost::lexical_cast    
易用吗,代码清晰明确吗
高效吗,无额外内存分配吗
长度安全吗
类型安全吗
是否可以用于模板之中
        是            是               否                    否                      是
        是            是               否                    是                      否
        否            是               是                    是                      是
        否            否               是                    是                      是
        否            否               是                    是                      是

 

准则

  • 如果你所要做的只是将一个值转换为一个字符串的话,尽量默认使用boost::lexical_cast。
  • 如果想进行简单的格式化,或者需要支持宽字符串,或者在模板当中进行格式化,尽量使用stringstream或strstream。
  • 如果想进行一些复杂的格式化,停驶并不需要支持宽字符串,也不想让代码用在模板当中的话,尽量使用snprintf。
  • 永远不要使用sprintf。

 

第四条 标准库成员函数

使用标准库里面的mem_fun,将成员函数适配为仿函数(functor),从而可被标准库算法以及其他正常情况下只适用自由函数的代码所使用。但不要将它用在标准库自己身上。

 

第五条 第六条 泛型性的风味

 

准则如果你的类型有一个更好的途径可以用于交换其对象的值的话,请考虑为它特化std::swap()。

 

// 示例:重载swap()。
 
class X {
public:
    void swap(X&);
};
 
// 注意:这个重载并没有放在std名字空间当中。请参考名字空间Koenig查找。
swap(X&a, X&b) {
    a.swap(b);
}

 

第七条 泛型性的风味

 

准则记住,函数模板不能偏特化,只能重载。写一个看似函数模板偏特化的函数模板实际上是在写一个单独的主函数模板。

 

 

在如下代码中,最后一行代码调用的是f()的那个版本?

// 示例:函数特化。
 
template <typename T>    // (a):一个主模板
void f(T);
 
template <typename T>    // (b):一个主模板,重载了(a)
void f(T*);
 
template <>              // (c):(a)的一个显示特化(全特化)
void f<int*>(int*);
 
template <>              // (d):(b)的一个显示特化(全特化)
void f<int>(int*);
 
// ....
 
int * p;
f(p);                    // 调用的哪个版本?

答案是……调用(d),想必结果是你意料之中。如果我们注释掉(d),f()调用的又是哪个版本呢?答案是……调用(b),是不是让你大吃一惊?如果这令你感到惊讶的话,你并不是唯一一个,当时它曾让许多专家大吃一惊。理解这个例子的关键其实很简单:模板特化并不参加重载。

 

 

准则记住,函数模板特化并不参与重载决议。只有在某个主模板被重载决议选中的前提下,其特化版本才有可能被使用。而且,编译器在选择主模板的时候并不关心它是否有某个特化版本。

 

让我们来看看函数模板的重载规则。

  • 非模板函数是C++中的一等公民。如果一个普通的非模板函数跟一个函数模板在重载解析的参数匹配中表现一样好的话,编译器会选择普通函数。
  • 如果编译器没有发现合适的一等公民,那么主函数模板作为C++中的二等公民就会被纳入考虑。具体选择哪个主函数模板则取决于哪个的参数类型匹配的最好。
  • 如果这个被选中的主函数模板碰巧有针对所使用的模板实参做了特化的话,该特化版本才会被编译器选中。否则编译器将使用以正确类型实例化的主模板。
  • 等等

 

 

准则如果你想要将一个主模板特化,同时又希望该特化版本能够参与重载决议(或希望确保当它完全匹配用户调用时能被编译器选中)的话,只需将其写成普通函数即可。

推论:如果你确实提供了某个函数模板的重载,那么你应当避免为它提供特化。

 

 

准则如果你正在写一个可能需要被特化的主函数模板的话,请尽量将它写成一个孤立的、永远不该被特化或重载的函数模板,并将其具体实现全部放入一个包含了一个静态函数的类模板当中。这么一来任何人都可以对后者进行特化(全特化或偏特化),而同时又不会影响到主函数模板的重载决议。

 

// 示例:函数特化。
 
template <typename T>
struct X;
 
template <typename T>            // 请不要动这个函数
void f(T t) { X<T>::far(t); }
 
template <typename T>            // 可以对这个函数进行特化
struct X {
    static void f(T t);
};

 

第八条 友元模板

准则明确地表达出你的意图。如果你所指的确实是一个模板,可以在模板名字后面加上模板实参列表(可以为空,即<>)。

 

// 示例:友元模板。
 
namespace boost {
    template <typename T> void checked_delete(T* x);
}
 
class Test {
    friend void boost::checked_delete<Test>(Test* x);    // 或"checked_delete<>(Test* x)"
}

 

第九条 第十条 导出限制

准则记住,export并不能带来像普通函数那样的真正的分离式编译。

准则记住,export只能隐藏依赖性,并不能消除依赖性。

准则(就目前而言)避免使用export。

 

第十一条 第十二条 异常安全问题

准则

  • 为应用程序或子系统确立一个整体的错误报告/处理策略,并始终遵循它。并为错误报告、错误传播以及错误处理制定策略。
  • 在那些侦测到错误而自身又无法对其进行处理的地方抛出异常。
  • 在那些具备有足够知识和上下文信息去处理/转换错误或强制实施边界条件的地方编写try/catch(例如,在子系统边界上或其他运行时防火墙边界上进行catch(…))。

准则函数应当总是支持它所能支持的最强的异常安全保证,但前提是不能给那些并不需要该保证的调用者带来额外开销。

准则永远不要允许析构函数、释放操作以及swap()函数抛出任何异常,因为否则的话,就没法安全且可靠地进行资源清理了。

准则

  • 永远不要为函数加上异常规格。
  • 除非你想声明的是空异常规格列表(即throw()),但仍不推荐这样做。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理