您的位置:首页 > 编程语言 > C语言/C++

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

2008-12-02 15:09 369 查看
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(尽可能推迟变量的定义)

无论什么时候定义一个带构造器和析构器的变量时,都会引发相应的开销(用于构造和析构)。因此,应该尽量避免这不必要的开销。优化做法:不要定义从来不使用的变量,这一点并不是任何时候都能做到。例如:

//This function defines the variable "encrypted" too soon
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 ncecessry to place an encrypted version of password in encrypted

return encrypted;
}

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

//将变量的定义推迟到使用前才定义
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 ncecessry to place an encrypted version of password in encrypted

return encrypted;
}

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

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

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

//This function postpones encrypted's definition until its' necessary, but
//it's still needlessly inefficient.
std::string encryptPassword(const std::string& password)
{
... //check length as above

std::string encrypted;

encrypted = password;

encrypt(encrypted);
return encrypted;
}

一个更好的作法如下:

//The best way to define and initialize encrypted
std::string encryptPassword(const std::string& password)
{
... //check length as above

std::string encrypted(password); //definie and initialize via copy constructor

encrypt(encrypted);
return encrypted;
}

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

但是对于循环呢?
例如:
//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,但是完全不用是不可能的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: