Prefer C++
Written by 李智勇
前言
读了《Eric Raymond对于几大开发语言的评价》一文,觉得其对C++的评价极其偏颇。C++本身支持多典范设计,也就是说你可以完全不用OO,GP而只采用结构化的方法去进行程序设计。这个时候同C相比,效率是基本一致的。而确可以享受到更严格的类型系统检查、inline函数、名字空间、运算符重栽所带来的益处。如果你牺牲一点效率,你还可以使用异常处理。
我以前为项目组成员写过一篇文章,号召大家从C转向C++。现贴出此文,望方家斧正。
1、比C更严格的类型系统
char szBuf[MAX_PATH];
WORD j=szBuf;
在C里面是允许的是个警告,而在C++里面则非法是错误。
而WORD Add(WORD a,WORD b);
Add(1,szBuf);可能导致灾难性的后果。
更关键的是处理回调函数时:
比如:API函数原型int SetAbortProc(HDC hdc, ABORTPROC lpAbortProc);
调用时有SetAbortProc(hdcPrn, AbortProc);
如果AbortProc与ABORTPROC类型不符,在c里边将自动转换类型。绝对可能导致灾难性的后果。此时堆栈会如何?
总之C++在防范失误所引起的错误方面做的更好,但绝对不禁止故意打破类型系统的行为。由于必须兼容C,C++做的也不好,警告后还是要做类型转换的。看下面这个例子:
int a=0;
unsigned b=0;
int i;
for(i=10; i>=b; --i)
{
a++;
}
其实这是个死循环。问题出在i和b类型不匹配(编译时这是个警告,而不是错误)。当必须进行比较时,都会被转成unsigned,于是i>=b将永远为true。
2、智能化的资源管理
利用C++的构造和析构函数可以建立非常完善的资源管理机制,尽可能的防止资源泄露。看下面的程序段:
void func1()
{
char* szBuf=malloc(50);//利用C中void* 到其他类型指针的自动转换
--------
free(szBuf);
return ;
}
这样在分配和释放内存之间的任何一个地方,如果需要返回,则必须有这种形式的代码:
if(---)
{ free(szBuf); return 0;}
而一旦忘记释放内存而返回,必将产生内存泄露。
C++类的实例有这样一种特性。在其作用域开始的时候,构造函数被调用,超出作用域时析构函数被调用。利用这一规律来管理资源的最有名的例子是标准库中的智能指针(参看:More Effevtive C++,The C++ Standard Library及The C++ Programming Language。
下面简要说明其原理:
Class A;
void func2()
{
A a; //构造函数被调用
------
return; //析构函数被调用
}
如果在Class A的构造函数中完成相关资源分配,在析构函数中释放内存会怎样?
分配内存后再不用担心释放的问题了。
A大致有如下的形式:
typedef struct tagPoint
{
int x;
int y;
}Point,PPoint;
Class A
{
PPpoint pointer;
SmartPointer(PPpoint p)
{
pointer=p;
}
~SmartPointer()
{
if(pointer)
delete pointer;
}
Point & operator*() const
{
return *pointer;
}
Point * operator->() const
{
return pointer;
}
};
这样用到Point时就可以这样:
A pointer(new Point);//为Point结构分配了内存
pointer->x=5; //存取相关元素
//超出作用域时为Point分配的内存将被自动删除
而为每一个结构都写一个类不划算,所以人们发明了模板。应用模板时A被定义成这个样子:
template <typename T> class SmartPointer
{
SmartPointer(T* p)
{
pointer=p;
}
~SmartPointer()
{
if(pointer)
delete pointer;
}
T& operator*() const
{
return *pointer;
}
T* operator->() const
{
return pointer;
}
private:
T* pointer;
};
这样不管什么结构,都可以用A了。
使用时是这样:
A<Point> pointer(new Point);
pointer->x=5;
//超出作用域时为Point分配的内存将被自动删除
N多人阐述过这个观点,但我觉得在windows下最有用的是附件1。
可实现对Windows下各种句柄的智能化管理。
作者:Jeffey Richter 2000年第四期msdn杂志,win32 Q&A专栏
读懂这个例子需要一点模板和符号重载的知识。
用起来很简单:
比如对于文件句柄,有:
CensureCloseHandle hHandle=CreateFile(---);
WriteFile(hHandle);
----------
不用调用CloseHandle了,超出作用域它将被自动调用。
最嚣张的应用是有引用记数的智能指针。
见附件2。(Come from the C++ standard library)
3、强大的inline(C不支持这个吧!)
第一次看Link-time Code Generation(Matt Pietrek MSDN magazine 2002 5)别的也没记住,就记了个通过链接器自主选择那些函数可以做inline可大幅度提高性能。后来自己就在vc++上试了一下,结果还是真恐怖,先说性能最多能提高多少,看下面的程序,并猜猜inline函数所花费的时间:
int add(int a,int b)
{
return a+b;
}
inline int addinline(int a,int b)
{
return a+b;
}
都是以这种形式进行调用:
int b;
for(int j=0;j<50000;j++)
{
b=add(60,70);
}
假设add()所花费的时间是:88964ns(vc++ 6.0 release 版本下),那么addinline所花费的时间是多少?
真实结果是0,也就是说addinline函数根本不存在。被优化后,剩下的只是一个值0x82。当然这是一个极端的例子,但从另一个角度讲把适当的函数做成inline实际上是给编译器,连接器做出更大优化的机会。
inline的另一个优势。
大家都知道,函数调用要处理堆栈(很少是寄存器)。这样就有一部分进栈和出栈的指令,这部分代码对于inline是没有的。
for(int j=0;j<50000;j++)
{
b=add(j,j+1);
}
与
for(int p=0;p<50000;p++)
{
b=addinline(p,p+1);
}
cout<<b;//必须使用一下b
此时所耗费的时间两者分别为:
1.10619e+006ns 和331754ns