Effective C++学习笔记(1)

最近刚看完Effective C++,记录一下当前几个比较经常使用的方法。程序员

1.以独立语句将newed对象置入智能指针

  • 智能指针是以对象管理资源,在构造函数中得到资源并在析构函数中释放资源​express

  • 如下调用:​安全

processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());​ide

  建立该调用的代码,编译器要作如下三件事:​函数

   (1)调用priority​布局

   (2)执行“new Widget”​spa

   (3)调用tr1::shared_ptr构造函数​设计

  C++编译器以什么次序完成这些事呢?弹性很大。能够肯定(2)必定在(3)以前,但(1)能够排在第一或第二或第三执行。若是编译器选择以第二顺位执行它,即(2)(1)(3)的顺序,万一对(1)的调用致使异常,此状况下“new Widget”返回的指针将会遗失,由于它还没有被置入tr1::shared_ptr内,而tr1::shared_ptr是咱们用来防卫资源泄漏的。因此对processWidget的调用过程可能引起资源泄漏。指针

  • 解决办法:使用分离语句,即分别写出(1)建立Widget,(2)将它置入一个智能指针内,而后再把那个智能指针传给processWidget:​code

   std::tr1::shared_ptr<Widget> pw(new Widget);​

   ProcessWidget(pw,priority()) ;​

  以上之因此行得通,是由于编译器对于“跨越语句的各项操做”没有从新排列的自由(自用在语句内它才有那个自由度

 

2.宁以pass-by-reference-to-const替换pass-by-value

考虑如下代码,其中调用validateStudent,后者须要一个Student实参(by value)并返回它是否有效:​

bool validateStudent(Student s);​

Student plato;​

Bool platoIsOK = validateStudent(plato);​

上述调用会发生如下:​

Student的copy构造函数会被调用,以plato为蓝本将s初始化。validateStudent返回后s会被销毁。所以对此函数而言,参数传递的成本是“一次Student copy构造函数调用,加上一次Student析构函数调用”.

  但那还不是所有。Student内有两个string对象,因此每次构造一个Student对象也就构造了两个string对象。此外,Student继承自Person,因此每次构造Student对象也必须构造出一个Person对象。一个Person对象又有两个string对象在其中,所以每一次Person构造动做又需承担两个string构造动做。​

  最终结果是,以by value方式传递一个Student对象会致使调用一次Studnt copy构造函数、两次Person copy构造函数、四次string copy构造函数。当函数内的Student复件被销毁,每一个构造函数调用动做都须要一个对应的析构函数动做。所以,以by value方式传递一个Student对象,整体成本是“6次构造函数和6次析构函数”!

 

如今考虑pass by reference-to-const:​

  bool validateStudent(const Student& s);​

这种传递方式的效率高得多:没有任何构造函数或析构函数被调用,由于没有任何新对象被建立。const是很重要的,它保护了传进去的参数不会被修改。​

另外,以by reference方式传递参数也能够避免slicing(对象切割)问题。当一个子类对象以by value方式传递并被视为一个基类对象,基类的copy构造函数会被调用,而“形成此对象的行为像个子类对象”的那些特化性质全被切割掉了,仅仅留下一个基类对象。​

 

3.将成员变量声明为private

1.语法一致性​

若是成员变量不是public,客户惟一可以访问对象的方法就是经过成员函数。客户对class成员的访问将统一经过成员函数来实现。​

2.使用函数可使你对成员变量的处理用更精确的控制。若是你令成员变量为pubilic,每一个人均可读写它,但若是你以函数取得或设定其值,你就能够实现出“不许访问”、“只读访问”、“读写访问”,甚至“只写访问”。​

3.封装。若是你经过函数访问变量,往后可改以某个计算替换这个成员变量,而class用户一点也不会知道class的内部实现已经起了变化。​

封装的重要性:若是你对客户隐藏成员变量,你能够确保class的约束条件老是会得到维护,由于只有成员函数能够影响他们。并且,你保留了往后变动实现的权利。若是不隐藏他们,即便拥有class源码,改变任何public事物的能力仍是极端受到束缚,由于那会破坏太多客户码。Public意味不封装,而几乎能够说,不封装意味着不可改变,特别是对被普遍采用的class而言。被普遍使用的class是最须要封装的一个族群,由于他们最可以从“改变用一个较佳实现版本”中获益。​

假设咱们有一个public成员变量,而咱们最终取消了它。全部使用它的客户码都会被破坏,而那是一个不可知的大量。​

protected并不更好。假设咱们有一个protected成员变量,而咱们最终取消了它。全部使用它的子类都会被破坏,而那每每也是是一个不可知的大量。​

从封装的角度来讲,其实只有两种访问权限,private和其余。

 

4.尽量延后变量定义式出现的时间

只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序的控制流到达这个变量定义式时,你便得承受构形成本;当这个变量离开其做用域时,你便得承受析构成本。即便这个变量最终并未被使用,仍需耗费这些成本,因此应当尽量避免这种情形。

更,不仅应该考虑延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到可以给它初值实参为止。这样不只可以避免构造(和析构)非必要的对象,还能够避免无心义的default构造行为。

以上两种写法的成本以下:​

(1)方法A:1个构造函数+1个析构函数+n个赋值操做​

(2)方法B:n个构造函数+n个析构函数

若是class的赋值成本低于一组构造+析构成本,作法A大致而言比较高效。尤为当n值比价大时。不然B比较好。此外方法A形成名称w的做用域比方法B大,有时那对程序的可理解性和易维护性形成冲突。所以,除非(1)你知道赋值成本比“构造+析构”成本低,(2)你正在处理代码中效率高度敏感的部分,不然你应该使用方法B

 

5.尽可能少作转型动做

C风格的转型:​

(T)expression   //将expression转型为T​

函数风格的转型:​

T(expression)  //将expression转型为T​

以上两种称为“旧式转型”,C++的四种新式转型:​

(1)const_cast<T>(expression)​

一般用来将对象的常量性转除。​

(2)dynamic_cast<T>(expression)​

主要用来执行“安全向下转型”,也就是用来决定某对象是否属于继承体系中的某个类型。将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来作相应处理。是惟一可能耗费重大运行成本的转型动做。​

(3)reinterpret_cast<T>(expression)​

T 必须是一个指针、引用、算术类型、函数指针或者成员指针。它能够把一个指针转换成一个整数,也能够把一个整数转换成一个指针​

(4)static_cast<T>(expression)​

用来强迫隐式转换,例如将non-const对象转为const对象,或将int转为double等等。还能够用来执行上述多种转换的反向转换,例如将void*指针转为typed指针,将point-to-base转为pointer-to-derived。

 

转型并不是什么都没有作。有的时候须要内部会有一个偏移量来实现。对象的布局方式和它们的地址计算方式随编译器的不一样而不一样,那意味着“因为知道对象布局”而设计的转型,在某一平台行得通,在其余平台并不必定行得通。

dynamic_cast的许多实现版本执行速度至关慢。例如至少有一个很广泛的实现版本基于“class名称之字符串比较”,若是你在四层深的单继承体系内的某个对象身上执行dynamic_cast,这个实现版本每一次dynamic_cast可能会好用多达四次的strcmp调用,用以比较class名称。深度继承或多重继承的成本更高。

【其实这个的内容蛮多,我只把以为重要的两点写了下来】

 

因此,

(1)若是能够,尽可能避免转型,特别是在注重代码效率的代码中避免dynamic_cast。若是有个设计须要转型动做,试着发展无需转型的替代设计。​

(2)若是转型是必要的,试着将他们隐藏于某个函数背后。客户随后能够调用该函数,而不需将转型放进他们本身的代码内。​

(3)宁肯使用C++-style转型,不要使用旧式转型。前者很容易辨识出来,并且也比较有着分门别类的执掌。

 

6.避免返回handles指向对象内部成分

这段代码能够经过编译。但实际上它是自我矛盾的。一方面upperleft和lowerRight被声明为const成员函数,由于它们的目的只是为了提供客户一个得知Rectangle相关坐标点的方法,而不是让客户修改Rectangle。另外一方面两个函数却都返回reference指向private内部数据,调用者因而经过这些reference更改内部数据。如:​

Point coord1(0,0);​
Point coord2(100,100);​
const Rectangle rec(coord1,coord2);​

rec.upperLeft().setX(50);​

这里,upperLeft的调用者可以使用被返回的reference来改变成员。但rec其实应该是不可改变的(const)。

上面这种状况是因为“成员函数返回reference”。若是它们返回的是指针或迭代器,相同的状况仍是发生,缘由也相同。reference、指针和迭代器都是所谓的handle,而返回一个“表明对象内部数据”的handle,随之而来的即是“下降对象封装性”的风险。​

要解决以上矛盾,只要对它们返回的类型加上const便可:

即便如此,仍是可能在其余场合带来问题。

明确的说,他可能致使空悬的handle:这种handle所指的东西(所属对象)不复存在。

例如某个函数返回GUI对象的外框,

例如某个函数返回GUI对象的外框,如今客户有可能这么使用这个函数:

对boundingBox的调用得到一个新的、暂时的Rectangle对象。这个对象没有名称,权且称他temp。随后upperLeft做用于temp身上,返回一个reference指向temp的内部成分,具体说是指向一个用一标志temp的Point。因而pUpperLeft指向那个Point对象。目前为止一切都还好。

可是,在哪一个语句结束以后,temp将被销毁,而那将直接致使temp内的Point析构。最终将致使pUpperLeft指向一个再也不存在的对象。

 

7.不要忽略编译器的警告

这里但愿以D::f从新定义virtual函数B::f,但其中有个错误:B中的f是个const成员,而在D中它未被声明为const。有些编译器可能这样说:​

warning:D::f() hides virtual B::f​

有些程序员对这个信息的反应是:“噢,固然,D::f遮掩了B::f,那正是想象中该有的事!”错,这个编译器视图告诉你声明与B中名称为f的全部函数并未在D中被从新声明,而是被整个遮掩掉了。

 

为了让被这样的名称重见天日,可以使用using声明式或转交函数。【这是另一条了】

相关文章
相关标签/搜索