永恒与瞬间之美(C++技术小品文)

类别:编程语言 点击:0 评论:0 推荐:

关键字:C++、静态对象、临时对象

本文将以一个简单的例子逐步地讲解静态对象和临时对象在C++程序设计中的使用技巧。

故事的开始

一天,我在写一个有复杂内存结构的MFC程序,为了看到我关心的内存数据结构内容,我要频繁地将大量的数据TRACE到屏幕上来。当我这么做时,天哪,我的CPU占用率达到了100%!在悲叹VC.NET的效率和我的老爷机的同时,我请出了我的珍藏法宝“DBWIN”,可是没想到,效率并没有太多提高,还使我的老爷机上所有运行着的DEBUG版程序把信息全都打印在“DBWIN”的窗口上,于是,我痛下决心,自己写一个TRACE。我的想法很简单,申请一个Console,将信息写上去。

第一版:丑小鸭的诞生

我用最简单的技术,大体上需要在程序运行开始时加入一个Console,在结束时自动释放掉。所以我利用了一下C++对象特性。写了一个类:

class MyConsole

{

public:

MyConsole()

{

m_bHaveConsole=AllocConsole();

}

~MyConsole()

{

if(m_bHaveConsole)

FreeConsole();

}

BOOL m_bHaveConsole;

};



有了它之后,只要定义一个全局类对象,就拥有了程序开始得到控制台,结束自动释放的好处。然后添加了如下的代码:

#include <conio.h>

#undef TRACE

#define TRACE _cprintf

在XApp.cpp定义全局的theApp前写上:

MyConsole theConsole;//这样我们拥有了一个全局可用的控制台!!!

XApp theApp;


好了,编译、运行,现在,所有的TRACE都打印在Console上了。

可是,第一只丑小鸭有很大缺点,首先,每次使用之前打扰了用户的代码,我们都想写用户仅需#include头文件就能运行的代码,而这里必须在CPP文件中加入MyConsole theConsole,这让我心如油煎。可是,该对象实体不能加在.H文件中。我开始想到的是静态类成员,可是这样的.H不能包含在两个以上的.CPP中,否则链接会有错误。于是,我想到了静态类对象。

第二版:成为天鹅前的涅槃

我把我的MyTrace.H改为如下内容

#ifndef MYTRACE_HPP

#define MYTRACE_HPP

#include <conio.h>

class MyConsole

{

public:

MyConsole()

{

m_bHaveConsole=AllocConsole();

}

~MyConsole()

{

if(m_bHaveConsole)

FreeConsole();

}

BOOL m_bHaveConsole;

};

class MyTraceCondition

{

public:

static Fun()

{

static MyConsole theConsole;

}

};

#undef TRACE

#define TRACE MyTraceCondition::Fun();_cprintf

#endif


这里我第一次用到了静态类对象,注意它与全局静态对象(包括静态类成员其实也是全局静态的一种变形)的区别。静态类对象在第一次使用时被构造,在以后的反复使用中并不被重新创建,也就是说,你可以这样来验证:

MyConsole()

{

m_bHaveConsole=AllocConsole();

_cprintf(“alloc\n”);

}

~MyConsole()

{

if(m_bHaveConsole)

{

_cprintf(“free\n”);

FreeConsole();

}



你会发现仅在第一次调用TRACE时,会输出“alloc”。有了静态对象的使用,我们可以把一个对象实体隐藏在.H文件中,不需要打扰用户的代码了。而且该方法还是高效的,效率和全局对象一样高。但是,当我的工程中运行两个以上线程都要用到TRACE时,我又有了新问题。你看到了,这种调用TRACE的方式并非线程安全,至少会使打印出来的信息杂乱地混在一起。你也许想到了使用线程安全地CRT版本,千万别那样做,多线程的代码大部分是不需要互斥的,如果为了一点代码的安全而导致整体代码效率降低,不值得。

于是我开始了在.H文件中添加临界区。要求所有的线程在调用TRACE中的_cprintf时进入相同的临界区。于是我必须对_cprintf进行点儿包装。我不能使用全局临界区,因为这样会导致我不得不搞出一个.CPP文件,同样我也不能使用使用静态类成员。那么使用普通的类成员做临界区吗?NO!,这样每次调用的就不是一个临界区了。于是,也许你猜到了。

——我第二次使用了静态对象!

#include <atlcore.h>

class MyTrace

{

public:

MyTrace():pTraceFun(_cprintf)

{

static CComAutoCriticalSection TheCriticalSection;

pCriticalSection=&TheCriticalSection;

pCriticalSection->Lock();

}

~MyTrace()

{

pCriticalSection->Unlock();

}

int(*pTraceFun)(const char*,...);

private:

CComAutoCriticalSection * pCriticalSection;

};

现在,不论你怎么使用,都会进入相同的临界区。

好吧,现在#define TRACE…,噢,gold!我犯了极大的错误,我简直没法写出对TRACE的重定义。怎么利用MyTrace呢?难道写成

#define TRACE MyTraceCondition::Fun(); \

MyTrace tracetmp; \

(*tracetmp. pTraceFun)


不,我无法知道用户将怎样使用TRACE。也许他们更愿意在一对{}中使用N次,那这段代码无疑成了毒瘤。

于是,灵光一闪,我发现了成为天鹅的关键

第三版:成为漂亮的天鹅

哈哈,我将头文件内容改为这样

#ifndef MYTRACE_HPP

#define MYTRACE_HPP

#include <conio.h>

#include <atlcore.h>

class MyTrace

{

public:

MyTrace():pTraceFun(_cprintf)

{

static CComAutoCriticalSection TheCriticalSection;

pCriticalSection=&TheCriticalSection;

pCriticalSection->Lock();

}

~MyTrace()

{

pCriticalSection->Unlock();

}

int(*pTraceFun)(const char*,...);

private:

CComAutoCriticalSection * pCriticalSection;

};

class MyConsole

{

public:

MyConsole()

{

m_bHaveConsole=AllocConsole();

}

~MyConsole()

{

if(m_bHaveConsole)

FreeConsole();

}

BOOL m_bHaveConsole;

};

class MyTraceCondition

{

public:

static MyTrace Fun()

{

static MyConsole theConsole;

return MyTrace();

}

};

#undef TRACE

#define TRACE (*MyTraceCondition::Fun().pTraceFun)

#endif


哈哈,你看到了,我运用了临时对象的特性,临时对象在失去外部的引用环境时(在本例中即在TRACE执行完时)自动析构,这样以上就为我们自动生成了一个临界区。并且没有显式的对象产生。在MyTrace的构造和析构函数中打印点儿东西,你同样可以看到临时对象的构造和析构过程。
总结经验

临时对象往往是要谨慎对付的家伙,静态对象也不是所有的人都清楚的,但作为C++的FUN,总是需要创造性的思维来利用语言的强大特性,对语言的一些容易出错的细节合理利用往往可以使程序妙笔生花。希望看了这篇文章,对你有所帮助,C++万岁。

我歌月徘徊

我舞影凌乱


本文地址:http://com.8s8s.com/it/it26096.htm