Effective C++ 读书笔记之implemenations(1)

来源:互联网 发布:刷赞软件 编辑:程序博客网 时间:2024/06/03 22:53
Implementations

Defining variables too soon can cause a drag on performance.
Overuse of casts can lead to code that's slow, hard to maintain and infected with subtle bugs;
Returning handles to an object's internals can defeat encapsulation and leave clients with dangling handles;
Failure to consider the impact of exceptions can lad to leaked resourcs and corrupted data sturctures.
Overzealous inlining(过度内联) can cause code bloat;
Excessive coupling(过度藕合) can result in unacceptably long build times.


Item 26: Postpone variable definitions as long as possible(尽可能推迟变量的定义)

无论什么时候定义一个带构造器和析构器的变量时,都会引发相应的开销(用于构造和析构)。因此,应该尽量避免这不必要的开销。优化做法:不要定义从来不使用的变量,这一点并不是任何时候都能做到。例如:
  1. //This function defines the variable "encrypted" too soon
  2. std::string encryptPassword(const std::string& password)
  3. {
  4.     using namespace std;
  5.     string encrypted;
  6.     if(password.length()<MinimumPasswordLength)
  7.     {
  8.         throw logic_error("password is too short");
  9.     }
  10.     ...        //Do whatever is ncecessry to place an encrypted version of password in encrypted
  11.     return encrypted;
  12. }



在上面代码中,串encrypted并不是从来没有被使用,但是一旦产生异常logic_error,则没有被使用。但是仍然会被构造和析构。比较好的作法:

  1. //将变量的定义推迟到使用前才定义
  2. std::string encryptPassword(const std::string& password)
  3. {
  4.     using namespace std;
  5.     if(password.length()<MinimumPasswordLength)
  6.     {
  7.         throw logic_error("password is too short");
  8.     }
  9.     string encrypted;
  10.     ...        //Do whatever is ncecessry to place an encrypted version of password in encrypted
  11.     return encrypted;
  12. }


上面的代码仍然不是最为有效的,因为串encrypted没有带初始化参数,这意味着它仍然会调用缺省的构造器(constructor),然后才调用赋值构造器(copy constructor)。例如我们假定encryptPassword省略的部分是通过以下函数实现的:

void encrypt(std::string& s);                //encrypts in place

下面的是我们通常的做法:

  1. //This function postpones encrypted's definition until its' necessary, but
  2. //it's still needlessly inefficient.
  3. std::string encryptPassword(const std::string& password)
  4. {
  5.     ...        //check length as above
  6.     std::string encrypted;
  7.     encrypted = password;
  8.     encrypt(encrypted);
  9.     return encrypted;
  10. }


一个更好的作法如下:
  1. //The best way to define and initialize encrypted
  2. std::string encryptPassword(const std::string& password)
  3. {
  4.     ...        //check length as above
  5.     std::string encrypted(password);        //definie and initialize via copy constructor
  6.     encrypt(encrypted);
  7.     return encrypted;
  8. }


总结:上面这段代码才显示了标题的意义,不仅将变量的定义推迟到定义同时初始化。

但是对于循环呢?
例如:
//Approach A: define outside loop
widget w;
for(int i=0;i<n; ++i)
{
    w=some value dependent on i;
  ...
}

//Approach B: define inside loop
for(int i=0;i<n; ++i)
{
   widget w=some value dependent on i;
  ...
}

对于上面widget对象,两种方法的开销如下:
方法 A: 1 construct + 1 destructor + n assignments
方法 B: n constructors + n destructors

因此,类的赋值开销少于类的构造/析构,则方法A更有效。相反,则默认情况下应该选择方法B。因为方法B还有一个优势,相对于A来说,对象w有更小的可视范围,这样程序的可读性和可维护性要好。


Item 27: Minimize casting(减少类型转换)

C++设计的原则是保证不可能出现类型错误,但是强制类型转换可能会破坏类型系统。如果你从C,JAVA或者C#转到C++,你可能会意识不到这一点。因为对于C,JAVA和C#来说,类型转换是必须的,并且危险性较小。对于C++来说,则必须小心处理。

类型转换的语法有三种形式:
(1) (T) expression       
(2) T(expression)
这两种没有大在区别,是C语言的老方式。

C++又提供了四种新的方式:

const_cast<T>(expression)        //唯一用于转换成常量对象的方法
dynamic_cast<T>(expression)        //执行"安全向下的类型转换“,即决定某一对象是否为一种特殊类的继承对象
reinterpret_cast<T>(expression)    //低层次的转换,如将指针转化为int
static_cast<T>(expression)        //用于强制性的隐式转换,如non-const对象到const对象,int到double等。或者相反的转换都可以(但const到non-const除外,它必须由const_cast来实现)。

一般情况下,推荐尽量使用新的C++方式,而不是C方式。
本书中唯一的用到老的方式如下:将一个对象作为参数传递给一个函数。
class Widget
{
    public:
     explicit Widget(int size);
     ...
};

void doSomeWork(const Widget& w);

doSomeWork(Widget(15));            //Create Widget from int with function-style cast

doSomeWork(static_cast<Widget>(15));    //Create Widget from int with C++ style cast

许多程序员认为cast只是告诉编译器将一种类型当另一种类型来处理,其实是错误的。cast常常使得代码在run-time时才执行。
例如:

int x,y;
...
double d = static_cast<double>(x)/y;        //divide x by y;

上面的类型转换也会产生代码,因为在大多数计算机架构中,int和double在底层的表示是不一样的。一个更能说明问题的例子如下:
class Base {...};
class Derived: public Base {...};

Derived d;
Base *pb = &d;            //implicitly convert Derived* -> Base*

上面代码创建了一个指向继承类的变量d和一个指向基类的指针pd,注意有时候这两个指针的值是不相同的,也就是说同一个对象不仅一个地址,这在C,JAVA和C#里面都是不会发生的,但是在C++里面可能发生。如果发生这种情况,往往要在Derived* 指针基础上加一个偏移才能得到Base*指针的值(具体如何计算根据编译器不同而不同)。实际上,在多继承中的虚拟就是这种情况,在单继承中发生这种情况也是可能的。

关于Cast一个比较有趣的事情是应用柜架的定义。例如,继承内中的一个虚函数首先要调用基类中的函数,

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

    ...
};

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

注意上面的cast调用,它实际上没有调用Base的onResize,而是调用一个新的原来地基类对象的copy对象,换句话,它调用的是当前类SpecialWindow的对象中拷贝的Window部分。

正确的方式如下:
class SpecialWindow: public Window
{
public:
    virtual void onResize()
    {
        Window::onResize();                //cast Window::onResize
                                //This works!
        ...
    }
    ...
}

Dynamic_cast的用法

 要注意dynamic_cast的实现是非常慢的,例如实现中至少有一个共同的操作是类名字(字符串)的比较。打个比方,如果是一个有四层深的单继承类,那么一个dynamic_cast要调用四次strcmp比较函数。因此,dynamic_cast的开销非常大的,在对性能比较注重的应用中要小心使用。

dynamic_cast通常用在基类要调用继承类中的功能,例如:上面的Window类要调用SpecialWindow类中的blink函数
class Window                //Base class
{
 public:
    ...
};

class SpecialWindow: public Window
{
public:
    void blink()
    ...
}

有两种方法可以达到该目的:(1)利用智能指针来存储继承类对象;(2)在基类中提供虚函数接口;
(1) 利用智能指针向量

typedef std::vector<std::tr1::shared_ptr<Window>> VPW;

VPW winPtrs;
...

for(VPW::iterator iter=winPtrs.begin();                //undesirable code: uses dynamic_cast
    iter!=winPtrs.end();
    ++iter)
{
    if(SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get()))
        psw->blink();
}

比较好的实现方法:
typedef std::vector<std::tr1::shared_ptr<SpecialWindow>> VPSW;

VPSW winPtrs;
...

for(VPSW::iterator iter=winPtrs.begin();                //undesirable code: uses dynamic_cast
    iter!=winPtrs.end();
    ++iter)
{
    (*iter)->blink();
}

但是如果Window存在多个继承类,则这时要定义多个容器(Container)来存储指针。

(2)一种替代的方法是在Window类中定义一个虚函数,作为继承类的接口。
class Window                //Base class
{
 public:
    virtual void blink()    {}            //default impl is no-op;
    ...
};

class SpecialWindow: public Window
{
public:
    virtual void blink()        {...}        //blink impl.
    ...
}

调用过程如下:

typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW winPtrs;
...
for(VPW::iterator iter=winPtrs.begin();                //undesirable code: uses dynamic_cast
    iter!=winPtrs.end();
    ++iter)
{
    (*iter)->blink();                        //note lack of dynamic_cast
}


总结:好的C++程序总是尽量少的用cast,但是完全不用是不可能的。