Effective C++学习笔记之第五章(1)

chapter 5:实现

item26:尽可能延后变量的定义
1)如果一个函数中可能抛出异常,那么在确定你必须用这个变量的时候定义这个变量,比如:

//变量放在异常捕捉之前,如果有异常抛出,那定义的这个变量就没有被使用过。
std::string encryptPassword(const std::string& password)
{
  using namespace std;
  string encrypted;
  if (password.length() < MinimumPasswordLength) {
      throw logic_error("Password is too short");
  }
  ...// do whatever is necessary to place an encrypted version of password in encrypted
  return encrypted;
}
//变量放在异常捕捉之后,避免不必要的定义
std::string encryptPassword(const std::string& password)
{
  using namespace std;
  if (password.length() < MinimumPasswordLength) {
      throw logic_error("Password is too short");
  }
  string encrypted;
  ...// do whatever is necessary to place an encrypted version of password in encrypted
  return encrypted;
}
2)既然定义了一个变量,肯定是要用它的。那么如果先定义再赋值的开销是调用默认构造函数和赋值函数,这肯定比直接调用拷贝构造函数的开销大。所以在变量定义的时候就用copy构造函数初始化它,避免默认构造函数的调用。
3)如果是对于循环的情况,考虑下面两种代码,一种是将变量定义放在循环体外,一种是将变量直接放在循环体内定义。
//方法A,开销是1个构造函数+1个析构函数+n个赋值函数
Widget w;
for (int i = 0; i < n; ++i)
{         
  w = some value dependent on i;       
  ...                                  
} 
//方法B,开销是n个构造函数+n个析构函数
for (int i = 0; i < n; ++i) 
{
  Widget w(some value dependent on i);
  ...
}

到底哪一个方法更好呢?除非预先知道赋值的成本比"构造+析构"成本低,或者说你正在处理代码中效率高度灵敏的部分,否则应该使用方法B。

item27 尽量少做转型
1)首先应该明白转型破坏了类型系统

//C风格的转型,两种方式,等价的
(T) expression or (T) expression
//C++提供的新式转型
const_cast<T>(expression)
dynamic_cast<T>(expression)
reinterpret_cast<T>(expression)
static_cast<T>(expression)

首先了解C++新式转型用途:
const-cast:解除对象的constantness。
dynamic-cast:提供安全的向下转型。比如基类转化为派生类。
reinterpret-cast:执行低级转型,实际动作可能取决于编译器,这也表示它不可移植。比如说将一个指针转化成int。
static_cast:强制类型转化,比如说int转成double,或者non-const转成const。
如果不得不用转型的时候,最后用C++的新式转型,因为它更容易被用户或者编译器识别,当然每种转型也更有针对性,更容易诊断出错误的应用。
2)任何一个类型转换往往令编译器编译出运行期间执行的代码。

class Base { ... };
class Derived: public Base { ... };
Derived d;
Base *pb = &d; // implicitly convert Derived* to Base*

这里建立了一个基类指针指向派生类的对象,但是有的时候上述两个指针值并不相同。这种情况下会有个偏移量(offset)在运行期间被施行于派生类指针的身上,用来取得正确的基类的指针值。这说明单一对象(比如刚刚说的派生类的对象)可能拥有一个以上的地址(以基类指针指向它时的地址和以派生类指针指向它时的地址)。(看到这里其实我想起来微软今年实习生的那道笔试题,可能原理还是没能理得太清楚)。实际上,一旦使用多重继承,这事几乎一直发生着,即使在单一继承中也可能发生。当然这里说的只是有可能有那个偏移量。对象的布局方式和它们的地址计算方式随编译器的不同而不同。
3)这里假设一个应用框架,在派生类中优先调用基类的onResize函数,看起来下面的实现方式是对的,实际上错了:

class Window {                     // base class
public:
  virtual void onResize() { ... }  // base onResize impl
  ...
};

class SpecialWindow: public Window {  // derived class
public:
  virtual void onResize() // derived onResize impl;
{ 
//cast *this to Window,then call its onResize;this doesn't work!do SpecialWindow-specific stuff。
    static_cast<Window>(*this).onResize();    
    ... 
  }       
  ...
};

class SpecialWindow: public Window {
public:
  virtual void onResize() {
   //回想当时写MFC程序的时候,C++重载某个基类的函数时,都会默认的加上这一句。
    Window::onResize();  // call Window::onResize on *this
   ...    
  }
  ...
};

实际上函数里面调用的只是当前对象转型后的一个window版本的副本,假设在调用window::onResize中做了某些修改,那跟当前的对象没半毛钱的关系。但是如果在后续中调用了SpecialWindow:onResize,并且也做出了修改,那这个修改就起作用了,但是这样只有部分修改起作用的结果就是原始的数据已经被破坏了。所以,把转型的动作替换成你想要实现的代码。
4)关于dynamic-cast另外需要注意的一点,dynamic-cast的许多实现版本执行速度都相当慢。至少一个普遍的实现版本是基于"class名称的字符串比较的。如果是四层深的单继承体系内某个对象身上执行dynamic-cast,那可能需要调用四次的strcmp函数。如果是多重继承,那成本更高。所以在避免使用转型的前提下,特别的避免使用dynamic-cast。
5)之所以需要dynamic-cast,是因为想在一个认定为派生类对象的身上执行派生类的操作函数,假设传进来的是一个指向基类的指针,那就必须要转换了。有两种一般性的作法可以避免,那就是使用容器并在其中存储直接指向派生类对象的指针(或者以前介绍的智能指针)。假设只有SpecialWindows有blink方法。

typedef std::vector<std::tr1::shared_ptr<SpecialWindow> > VPSW;
VPSW winPtrs;
...
// better code: uses no dynamic_cast
for (VPSW::iterator iter = winPtrs.begin();iter != winPtrs.end();++iter)
  (*iter)->blink();

当然这样的话,就不能在同一个容器中存储多种派生类的指针,或者说你需要多个容器。
6)当然,另一种方法就是在基类中提供一个虚函数,不做任何事,在派生类中定义想做的事,这就是常说的多态。

class Window {
public:
// default impl is no-op;see Item 34 for why a default impl may be a bad idea
  virtual void blink() {} 
  ...      
};  
class SpecialWindow: public Window {
public:
  virtual void blink() { ... };// in this class, blink does something
  ...       
};
typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
// container holds (ptrs to) all possible Window types
VPW winPtrs; 
... 
for (VPW::iterator iter = winPtrs.begin();iter != winPtrs.end();++iter) 
  (*iter)->blink();  // note lack of dynamic_cast
//多态对避免下面连串dynamic-cast的发生也很有效果
class Window { ... };
...  // derived classes are defined here
typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
  if (SpecialWindow1 *psw1 = dynamic_cast<SpecialWindow1*>(iter->get())) { ... }
  else if (SpecialWindow2 *psw2 = dynamic_cast<SpecialWindow2*>(iter->get())) { ... }
  else if (SpecialWindow3 *psw3 = dynamic_cast<SpecialWindow3*>(iter->get())) { ... }
  ...
}

7)如果实在需要转型,那就将他隐藏在某个函数的本后。用户可以调用该函数,而不需要将转型放进他们自己的代码中。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值