topameng's profileQuake3 启示录PhotosBlogListsMore ![]() | Help |
|
|
March 06 如何编写异常安全的C++代码http://tech.163.com 2006-04-17 09:19:59 来源: 网易学院(广州) 网友评论0 条 论坛关于C++中异常的争论何其多也,但往往是一些不合事实的误解。异常曾经是一个难以用好的语言特性,幸运的是,随着C++社区经验的积累,今天我们已经有足够的知识轻松编写异常安全的代码了,而且编写异常安全的代码一般也不会对性能造成影响。 3.需要一定的开销,频繁执行的关键代码段避免使用 C++异常处理机制. 7.将结构化异常处理结合/转换到C++异常对象,可以更好地处理WINDOWS程序出现的异常. 用得恰到好处,方显C++异常之美妙!catch(…) 1.7异常规范: 1.8陷阱:派生类中的异常规范 使用dbghelp获取调用堆栈--release下的调试方法学Author : Kevin Lynx当软件作为release模式被发布给用户时,当程序崩溃时我们很难去查找原因。常见的手法是输出LOG文件,根据LOG文件分析 要获取call stack(所谓的调用堆栈),就需要查看(unwind)stack的内容。We could conceivably attempt to unwind the StackWalk64声明如下: 具体每个参数的含义可以参见MSDN。这里说下ContextRecord参数,该参数指定了CPU各个寄存器的内容。StackFrame指定了stack frame的内容。stack frame是什么,我也不知道。(= =) StackWalk64函数需要用户指定当前frame的地址,以及当前程序的指令地址。这两个信息都被填充进ContextRecord,然后传进StackWalk64函数。 那么如何获取当前的stack frame地址和当前程序指令地址呢?如前所说,你可以使用内联汇编。(对于程序指令地址,因为要获取EIP寄存器的内容,而该寄存器不能被软件访问)也可以使用GetThreadContext一次性获取当前线程当前运行情况下的CPU各个寄存器内容。补充下,当前frame地址被放在EBP寄存器里,当前程序指令地址放在EIP寄存器里。但是,如同MSDN对GetThreadContext函数的说明一样,该函数可能获取到错误的寄存器内容(You cannot get a valid context for a running thread)。 另一种获取Context(包含EBP and EIP)的方法就是使用SEH(结构化异常处理),在__except中使用GetExceptionInformation获取。 GetExceptionInformation 传回一个LPEXCEPTION_POINTERS指针,该指针指向一个EXCEPTION_POINTERS结构,该结构里包含一个Context的指针,即达到目标,可以使用StackWalk函数。 补充一下,你可以直接使用StackWalk函数,StackWalk被define为StackWalk64(windows平台相关)。 unwind栈后,可以进一步获取一个stack frame的内容,例如函数名。这里涉及到SymFromAddr函数,该函数可以根据一个地址返回符号名(函数名)。还有一个有意思的函数:SymGetLineFromAddr,可以获取函数对应的源代码的文件名和行号。 当然,这一切都依赖于VC产生的程序数据库文件(pdb),以及提供以上API函数的dbghelp.dll。 参考一段简单的代码: #include <windows.h> #pragma comment( lib, "dbghelp.lib" ) void dump_callstack( CONTEXT *context ) sf.AddrPC.Offset = context->Eip; DWORD machineType = IMAGE_FILE_MACHINE_I386; HANDLE hProcess = GetCurrentProcess(); for( ; ; ) if( sf.AddrFrame.Offset == 0 ) pSymbol->SizeOfStruct = sizeof( symbolBuffer ); DWORD64 symDisplacement = 0; IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) }; if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) ) DWORD excep_filter( LPEXCEPTION_POINTERS lpEP ) dump_callstack( lpEP->ContextRecord ); if( SymCleanup( GetCurrentProcess() ) ) return EXCEPTION_EXECUTE_HANDLER; void func1( int i ) void func2( int i ) void func3( int i ) void test( int i ) int main() return 0; 以上代码在release模式下需要关掉优化,否则调用堆栈显示不正确(某些函数被去掉了?),同时需要pdb文件。当用户使用时可以只抛出挂掉时候的地址,然后开发者通过crashfinder这样的软件,启动拥有pdb的程序 April 21 dll 中 string 内存泄漏问题dll 导出函数即使间接引用的函数,如果输入参数为std::string 引用类型,那么该string 会引起内存泄漏。 April 18 错误c2059疯了,浪费了一个小时。删除掉了一个空行就出了这个问题。加回去正常编译。日 } name; March 25 VC的预编译功能这里介绍VC6的预编译功能的使用,由于预编译详细使用比较的复杂,这里只介绍几个最重要的预编译指令: /Yu, /Yc,/Yx,/Fp。其它的详细资料可以参考: MSDN -> Visual Studio 6.0 Document -> Visual C++ 6.0 Document -> VC++ Programmer Guider ->Compiler and Linker -> Details -> Creating Precompiled Header files February 21 stlport 标准库内存泄漏 使用stlport vector 居然发现了内存泄漏. STLport现在的做法是会特意泄漏内存的,如果你检测到内存泄漏,经过讨论,STLport采用了特意不回收收集的小块内存,而借助现代操作系统的自动回收能力(应用程序退出时会收回分配给进程的内存)的做法,以减少内存印记、获得较好的再分配能力和性能。具体的详细解释,可以读源码,上STLport的交流论坛,或者看《STL源码剖析》——里面有关于SGI内存适配器(也是STLport所采用的)的解析。 尽管泄漏一次不算什么内存泄漏,但当程序过大时候,可能内存泄漏数据多了起来会影响调试.也可以避免这种情况如 含有 int 的 vector resize 到 64 即可避免因小块内存而出现的内存泄漏,内存泄漏的一些调试方法可以使用vld 也可以用ms的调试函数. 可以参考前面的使用VLD检测内存泄露: http://topameng.spaces.live.com/blog/cns!F962D4854A8233D!341.entry November 01 使用VLD检测内存泄露1 要能够配置vld 即可以使用下面的宏。由于我的是vc2003 。而网上的是vc6和7编译出来的库,不知道是什么版本(可能是多线程调试,单线程调试之类设置不匹配),这些配置宏不起作用. 结果:运行结束后,在调试窗口打印所有泄漏内存中的数据,结束程序时间超长。
重复内存泄露在同一个地方,只显示一次堆栈信息。但包含泄露次数 跟踪的堆栈数目 #define VLD_MAX_TRACE_FRAMES 9 过少的话会发现没有调用函数堆栈显示。9 能显示到 new 的位置 打印泄露内存数据长度。有些地方泄露内存数据太大,如果全部打印,退出程序时间超长。 #define VLD_MAX_DATA_DUMP 10
测试自己
2 编译vld 需要配置vc include 路径,加入vc crt目录,例如:C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src (最好放到所有include目录最后面) 3 要注意不要把工程放入到中文目录,这样就不能看到泄露的函数堆栈了。那样还不如: #include "crtdbg.h" _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); 我在 dll 中使用vld 。当动态调用loadlibrary 来使用dll 时候。vld 不能报告内存泄漏 调试窗口内容: Dumping objects -> {353} normal block at 0x003B9C58, 320 bytes long. ~~~ 第353次内存分配发生内存泄漏 Data: < ; > F0 9C 3B 00 CD CD CD CD 00 00 00 00 CD CD CD CD { _CrtSetBreakAlloc(353); _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); } September 11 C语言中可变参数的用法(转载)
C语言中可变参数的用法 补充说明: #include <cstdlib> using namespace std; int getarg(int* arg) return 0; int _cdecl testarg(int t,char* name,...) getarg(&t); int main(int argc, char *argv[]) sp+32 parm1 September 05 c语言读取bmp文件格式//BMP 头 va_start (argptr,fmt); MessageBox(NULL,com_errorMessage,"错误提示",MB_ICONERROR); //*pic 返回 RGBA 类型像素数据,width 返回修正后的宽度,height返回高度 buf_p = buffer; bmpHeader.id[0] = *buf_p++; memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); if ( bmpHeader.bitsPerPixel == 8 ) if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) columns = bmpHeader.width; if(bmpHeader.bitsPerPixel == 24 ) if ( rows < 0 ) if ( width ) bmpRGBA = (byte*)malloc( numPixels * 4 ); for ( row = rows-1; row >= 0; row--) for ( column = 0; column < columns; column++ ) switch ( bmpHeader.bitsPerPixel ) case 24: free(buffer); July 19 堆栈丢失的错误 00000000() 程序报错,中断后堆栈中只有00000000().注意最后的()在vc里很容易被看成0. 造成这种错误的一种情况是: vc 优化与内存对齐 终于看到了quake3 的Renderer项目中的代码,发现了这样一段: if ( (int)tess.xyz & 15 ) { Com_Printf( "WARNING: tess.xyz not 16 byte aligned\n" ); } xyz 是一个矢量(float [4]) 数组。 当我自己的工程 Release 编译时,进入了这个警告。这个警告的意思是 tess的结构成员 xyz 所在的内存地址不是16byte对齐的。而对于quake3 的工程编译后却没有进入这个警告。直觉告诉我肯定是一些vc工程的设置问题,经过层层筛选,最后终于定位到。默认的vc release 工程,链接器-〉优化-〉引用:选择的是/OPT:REF,这个可以优化掉不用的函数减少文件大小。但Renderer 项目作为quake3 项目的依赖项,这样作用并不大。相反这个优化反而导致了内存变化。对于游戏程序来说,矢量对齐到16byte可以提高一定游戏性能。尽管可以在xyz 的声明前加上__declspec (align(16)),强制这个变量内存对齐到16byte边界。但需要修改的地方实在太多。 如果你的程序不需要计较多几K大小,这个选项还是选为默认吧。即不对其进行优化. 注意:由于quake3变量顺序安排的巧妙,才使tess.xyz 对齐到16位内存上的。如果改变了变量定义顺序,或者打开了上面的优化。都会导致tess.xyz不能对齐到16byte.如果自己不愿去计算,还是使用__declspec (align(16)) 吧 刚把所有blog 整理个列表,ms 就更新了。美其名曰:摘要,一页能浏览20个选项。真是。。。 July 14 巧用回收的指针指针的指针总是容易让人困惑,更能带给你惊奇. typedef struct nodetype { struct nodetype **head; } node_t; typedef struct static void free_ppnode(huff_t* huff, node_t **ppnode) //获取一个自由的node_t* 节点,用来存储 head (指向一个块中具有最大节点编号的节点) free_ppnode 每次回收ppnode的时候,在其指向的空间(nodePtrs[768] 中的元素)内容就不需要了,而这时修改ppnode指向空间中的数值(存放的是node_t * 变量,可以暂时认为其为32 位地址数字), 让其存放 freelist 变量值(可以暂时认为其是一个32位地址数字)。这样多次释放之后就形成了一个链表,但不需要任何其他的多余的元素协助。而普通链表中的需要 next 指针协助才能连接两个链表节点。 当然需要从链表取得下一个元素时,需要对 freelist 进行提领操作来获得以前的freelist 变量值 June 16 搜索文件目录获取文件列表//dest 写入的目标,size 目标大小,写入的格式,写入的内容
void Com_sprintf( char *dest, int size, const char *fmt, ...) { int len; va_list argptr; char bigbuffer[32000]; // big, but small enough to fit in PPC stack va_start (argptr,fmt);
len = vsprintf (bigbuffer,fmt,argptr); va_end (argptr); if ( len >= sizeof( bigbuffer ) ) { ASSERT("Com_sprintf: overflowed bigbuffer" ); } if (len >= size) { ASSERT ("Com_sprintf: overflow"); } strncpy( dest, bigbuffer, size-1 ); dest[size-1] = 0; } //字符串比较,忽略大小写
int Q_stricmpn (const char *s1, const char *s2, int n) { int c1, c2; do
{ c1 = *s1++; c2 = *s2++; if (!n--)
{ return 0; // strings are equal until end point } if (c1 != c2)
{ if (c1 >= 'a' && c1 <= 'z') { c1 -= ('a' - 'A'); } if (c2 >= 'a' && c2 <= 'z') { c2 -= ('a' - 'A'); } if (c1 != c2) { return c1 < c2 ? -1 : 1; } } } while (c1); return 0; // strings are equal
} int Q_stricmp (const char *s1, const char *s2)
{ return Q_stricmpn (s1, s2, 99999); } //basedir 根目录,subdirs 子目录,存放文件的列表,列表大小
//最开始选的目录为根目录,开始 subdirs 可设为 NULL
int Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **list, int *numfiles )
{ char search[MAX_OSPATH], newsubdirs[MAX_OSPATH]; char filename[MAX_OSPATH]; intptr_t findhandle; struct _finddata_t findinfo; if ( *numfiles >= MAX_FOUND_FILES - 1 )
{ return -1; } if (strlen(subdirs))
{ Com_sprintf( search, sizeof(search), "%s\\%s\\*", basedir, subdirs ); Com_sprintf( filename, sizeof(filename), "%s\\%s", basedir, subdirs ); } else { Com_sprintf( search, sizeof(search), "%s\\*", basedir ); //搜索的目录,* 表示搜多所有文件。*.mp3 即搜索mp3文件
Com_sprintf( filename, sizeof(filename), "%s", basedir ); } findhandle = _findfirst (search, &findinfo);
if (findhandle == -1) {
return 0; //如果是*.mp3 之类,没有任何文件 } do
{ if (findinfo.attrib & _A_SUBDIR) //是否为目录 { if (Q_stricmp(findinfo.name, ".") && Q_stricmp(findinfo.name, "..")) //不是. 或者.. { if (strlen(subdirs)) { Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s\\%s", subdirs, findinfo.name); } else { Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", findinfo.name); } Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles ); } } else { if ( *numfiles >= MAX_FOUND_FILES - 1 ) { return -1; } list[*numfiles] = new char[MAX_PATH]; Com_sprintf( list[*numfiles], MAX_PATH, "%s\\%s", filename,findinfo.name); //存入文件名到列表中 (*numfiles)++; } } while ( _findnext (findhandle, &findinfo) != -1 ); _findclose (findhandle);
return 0; } #define MAX_FOUND_FILES 0x1000 //处理不超过4096个文件。 char* list[MAX_FOUND_FILES];int count = 0; if(Sys_ListFilteredFiles(str,"",NULL,list,&count)) { MessageBox("文件过多,请重新选择更细的目录"); for(int i=0;i<count;i++) { delete list[i]; } } May 17 如何提高程序效率1 使用循环展开,对于循环内部只有一条语句可以采用循环展开来提升速度.
2 对于2的倍数除法使用&运算,如 30 除 4 的余数为 30 & 3,30 类最大的4的倍数为30 & ~3.一个数是否为2的幂 n & 1. 而判断一个变量中含有多少个1可以 3 对于一些只需要初始化一次的函数内部变量可以声明为static类型 4 对于过大的switch语句块,可以采用表驱动法,对于switch如果条件过多,后面的条件分支必须等待前面判断完毕才能执行。而采用表驱动可以直接跳转到要执行的语句 5 ^= 异或操作,切换某位状态由0到1,或者由1到0. A ^= 0x00010 ,迅速切换第2位 . 这样比 true ,false 之类的切换要快。写法也简单
6 ! 操作,如 strcmp 判断两个字符相等返回0,而不是 true ,而0值确对应 false. 和0比较相对于其他比较要快些。所以 if(!strcmp(a,b)),注意: ! 对于大于 0 的数返回 0,!0 怎返回 1
7 ~= 判断1个dword 变量x有多少个位为1。 int c=0; for(;x~= x-1;c++); 最后c 为 x 含有1 的个数。
熟练应用位操作有利于提高程序速度
May 14 c 不需要强制转换malloc的返回值
c++ 误区 fflush(stdin)来源:蚂蚁的 C/C++ 标准编程 作者:antigloss 等级:精品 1. 为什么 fflush(stdin) 是错的
首先请看以下程序:
#include <stdio.h>
int main( void ) { int i; for (;;) { fputs("Please input an integer: ", stdout); scanf("%d", &i); printf("%d\n", i); } return 0; }
这个程序首先会提示用户输入一个整数,然后等待用户输入,如果用户输入的是整数,程序会输出刚才输入的整数,并且再次提示用户输入一个整数,然后等待用户输入。但是一旦用户输入的不是整数(如小数或者字母),假设 scanf 函数最后一次得到的整数是 2 ,那么程序会不停地输出“Please input an integer: 2”。这是因为 scanf("%d", &i); 只能接受整数,如果用户输入了字母,则这个字母会遗留在“输入缓冲区”中。因为缓冲中有数据,故而 scanf 函数不会等待用户输入,直接就去缓冲中读取,可是缓冲中的却是字母,这个字母再次被遗留在缓冲中,如此反复,从而导致不停地输出“Please input an integer: 2”。
也许有人会说:“居然这样,那么在 scanf 函数后面加上‘fflush(stdin);’,把输入缓冲清空掉不就行了?”然而这是错的!C和C++的标准里从来没有定义过 fflush(stdin)。也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(linux 下的 gcc 就不支持),因为标准中根本没有定义 fflush(stdin)。MSDN 文档里也清楚地写着fflush on input stream is an extension to the C standard(fflush 操作输入流是对 C 标准的扩充)。当然,如果你毫不在乎程序的移植性,用 fflush(stdin) 也没什么大问题。以下是 C99 对 fflush 函数的定义:
int fflush(FILE *stream);
如果 stream 指向输出流或者更新流(update stream),并且这个更新流
If stream points to an output stream or an update stream in which
其中,宿主环境可以理解为操作系统或内核等。
由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用 fflush(stdin) 是不正确的,至少是移植性不好的。
2. 清空输入缓冲区的方法
虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。
if ( feof(stdin) || ferror(stdin) ) /* 不过会残留 \n 字符。 */ printf("%d\n", i); using std::cout; int main() // do something return 0; May 11 使用c函数获取文件大小取得当前文件指针位置函数: fseek(File, 0, SEEK_END); //移动到文件的结尾 int lastPos = ftell(File); //获得文件大小 fseek(File,currentPos,SEEK_SET); //恢复到原来的文件指针位置 return lastPos; 如果文件大于 4G ,就要用64 位的 __int64 _ftelli64( FILE *stream );
单字符宽字符互相转换#include <stdlib.h>
size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n); //转换单字符串为宽字符串 size_t wcstombs(char *mbstr, const wchar_t *wcstr, size_t count ); //转换宽字符串为单字符串
例如:
CString str = L"hello";
char sss[20]; wcstombs(sss,str.GetBuffer(),20); //转换宽字符为单字符 stl 宽字符到 单字符转换
wstring str = L"Hello";
std::wstring::size_type len = str.length(); std::string s(len*2,0); size_t total = wcstombs(&s[0],str.c_str(),len*2); s[total] = '\0'; return s; mbtowc 和 wctomb 是单个字符相互转换 int len; setlocale (LC_ALL, "chs"); //设置为简体中文环境
wchar_t wc = L'中'; wprintf(L"1个宽中文字符:%c \n",wc); char* p = "中"; len = mbtowc (&wc, p, MB_LEN_MAX); wprintf(L"单字符串转换为1个宽字符:%c 长度: %d\n",wc,len); char pcmb[MB_LEN_MAX]; len = wctomb (pcmb, wc); pcmb[len] = 0; printf("宽字符转换为单字符串:%s 长度:%d\n",pcmb,len); BYTE utf8[1024]; //utf8 字符串
wchar_t wstr[1024];
char mstr[1024]; //UTF-8 转换为宽字符 MultiByteToWideChar( CP_UTF8, 0, utf8,1024, wstr, sizeof(wstr)/sizeof(wstr[0]) ); WideCharToMultiByte( CP_ACP,0,wstr,-1,mstr,1024,NULL,NULL ); October 15 netting c++引用 lippman 文章 #include "TextQuery.h"
ref class TQL {
public:
// constructor creates a TextQuery object on native heap
// destructor frees it ...
TQL() { pTQuery = new TextQuery; }
~TQL() { delete pTQuery; }
// the operations we wish to publish within .NET
// these will generally be inlined by the compiler
void build_up_text(){ pTQuery->build_up_text(); }
void query_text() { pTQuery->query_text(); }
private:
// our native object through which to invoke
// our application compiled in Section 1 ...
TextQuery *pTQuery;
};
// Our Managed TQL Wrapper Put to Work ...
int main()
{
TQL ^tq = gcnew TQL;
tq->build_up_text();
tq->query_text();
return 0;
}
The first problem is pretty trivial, but it would be embarrassing should the class be made generally available because all types within a module default to internal scope. That is, they can’t be seen from outside the module. This is a good default because it prevents the global namespace of the application from being polluted by module-only types. You must explicitly declare as public all classes you want to make visible across modules: public ref class TQL {
public:
// no change ...
};
This also holds for native classes you compile under C++/CLI, which is the opposite of C++ visibility of native types. So if you need your native class to be public across modules, you’ll also need to label that class public. This is an extension to ISO-C++ in Visual C++® 2005. The second problem involves cleaning up resources other than memory from the common language runtime (CLR) heap, which is garbage collected. A garbage-collected heap has deep ramifications for destructors, and that’s where we’ll start. Under the Microsoft® .NET Framework, there is no notion of a destructor, so we’ve artificially mapped destruction to .NET, as I’ll explain shortly. First, remember that destruction is a two-phase operation. When you delete a pointer, for example, internally the compiler first checks to see that the pointer is not null. If it is, nothing else happens. Otherwise, the compiler invokes the destructor associated with the object being addressed. If that concludes without an exception, the actual heap memory is freed. For CLR types, this two-phase operation is carried out by two different components. The invocation of the destructor is carried out by the compiler. The actual freeing of the memory is always handled by the garbage collector. When you invoke a destructor in .NET, the actual memory associated with the reference object is not yet reclaimed and won’t be until the garbage collector kicks in. So, the problem with my initial implementation is that while each TQL object will eventually be reclaimed by the garbage collector, the second phase of destruction, it will not have the associated destructor invoked, the first and (from my point of view) more important phase of destruction. Before the memory associated with an object is reclaimed by the garbage collector, an associated Finalize method, if present, is invoked. Think of this method as a kind of super-destructor since it is not tied to the program lifetime of the object. This is referred to as finalization. The timing of when or whether a Finalize method is invoked remains undefined, meaning that garbage collection exhibits nondeterministic finalization. Nondeterministic finalization works well with dynamic memory management. When available memory becomes sufficiently scarce, the garbage collector kicks in and things pretty much just work. Under a garbage-collected environment, destructors to free memory are unnecessary. Nondeterministic finalization does not work well, however, when an object maintains a critical resource such as a database connection, native heap memory, or a lock of some sort. In such cases, you need to release that resource as soon as possible. In the native world, that is done through the pairing of a constructor and a destructor. As soon as the lifetime of the object ends, either through the completion of the local block within which it is declared or through the unraveling of the stack because of a thrown exception, the destructor kicks in and the resource is automatically released. This approach works very well, but was unfortunately missing in the CLR world. It soon became clear that programmers using .NET needed a canonical way of indicating that the potentially scarce resources held by objects of a type need to be quickly disposed of, and the design solution is the System::IDisposable interface with a single Dispose method that contains the clean-up code. The primary drawback of this solution is that Dispose requires an explicit invocation by the user. This is potentially error-prone and therefore a step backwards. The C# language provides a modest form of automation through a special using statement which, when followed correctly, generates the call to Dispose for the objects within the statement. Beginning with Visual Studio® 2005, Visual C++ instead translates the class destructor into the class Dispose method. The destructor is renamed internally to the Dispose method and the reference class is automatically extended to implement the IDispose interface: // internal transformation of destructor under C++/CLI
public ref class TQL : IDisposable
{
...
void Dispose() {
// suppress finalize method for this object
// then generate the user code ...
System::GC::SuppressFinalize(this);
delete pTQuery;
}
};
When either a destructor is invoked explicitly under C++/CLI or when delete is applied to a tracking handle, the underlying Dispose method is invoked automatically. If it is a derived class, a call of the Dispose method of the base class is inserted at the close of the synthesized method. While this transformation is a good thing, by itself it’s not good enough. First, reference objects don’t have scope constraints, so without explicitly having the programmer delete the reference object, the destructor doesn’t get invoked. Second, since the destructor now goes to Dispose rather than Finalize, the garbage collector is without any method to invoke. So, at first glance, this design change seems to have been a mistake! It is certainly the mistake I made in the implementation. As a native programmer, I didn’t realize that a destructor under .NET isn’t a complete solution to managing objects, due to the nature of garbage collection. Therefore, I didn’t think to explicitly provide a finalization function for the garbage collector to invoke. For just one object, probably nobody would notice. In a running system spawning a new TQL object for every query session, it would be quite an embarrassment. For your convenience, I redisplay the declaration of the TQL object: // the tq object never invokes the destructor ...
// and we have not provided a finalize method ...
// so the native memory held by tq is never freed ...
int main()
{
TQL ^tq = gcnew TQL;
tq->build_up_text();
tq->query_text();
return 0;
}
You need to provide a finalizer, and I’ll show you how in a minute. First let’s walk through how the CLR features in Visual C++ simulate deterministic finalization—by syntactically binding a reference object to a local or class scope; each represents a deterministic lifetime. The tricky part is that .NET itself doesn’t support this, and so we had to be clever. Visual C++ supports the declaration of an object of a reference class on the local stack or as a member of a class by declaring the object using the type name but without requiring its formal top hat (^). All uses of the object, such as invoking a member function, are done through the member selection dot (.) rather than arrow (->). At the end of the block, the associated destructor, transformed into Dispose, is invoked automatically: // OK, this invokes our destructor ...
int main()
{
TQL tq;
tq.build_up_text();
tq.query_text();
// destructor is invoked here ...
return 0;
}
For those with more of a library bent to your syntax there is an auto-handle<> template that functions equivalently. (I prefer going hatless in my designs.) As with the using statement within C#, this is syntactic sugar rather than defiance of the underlying .NET constraint that all reference types must be allocated on the CLR heap. The underlying semantics remain unchanged, except that the invocation of the destructor is automatic. In effect, the destructor is again paired with constructors as an automated acquisition/release mechanism tied to an object’s lifetime.
The problem with this solution is that you can’t force programmers to use it and, therefore, you can’t safely provide a destructor without providing a finalizer as well. Here’s how to do that: public ref class TQL {
public:
// constructor creates a TextQuery object on native heap
TQL() { pTQuery = new TextQuery; }
// destructor frees it
~TQL() { delete pTQuery; }
// finalizer frees it, called by garbage collector if
// destructor is not invoked ...
!TQL() { delete pTQuery; }
};
The ! prefix is meant to suggest the analogous tilde (~) that introduces a class destructor—that is, both post-lifetime methods have a token prefixing the name of the class. If the synthesized Finalize method occurs within a derived class, an invocation of the base class Finalize method is inserted at its end. If the destructor is explicitly invoked, the finalizer is suppressed.
There are some remaining issues, as always, with these sorts of language designs. A finalizer, in general, is inefficient. (For a great discussion of this issue, see the book CLR via C#, Second Edition, by Jeffrey Richter.) When possible, it’s preferable not to define one. But currently there is no way to restrict a class containing a destructor from always having its objects defined such that deterministic finalization is guaranteed. I can’t force users of my class to always declare local reference objects: void f()
{
TQL t; // ok, guaranteed to be disposed
TQL ^ht; // oops. No guarantee. Need a finalizer, then ...
...
}
One possibility would be to play with the accessibility of the finalizer to indicate whether you want to allow users to invoke it. That is, placing a finalizer in a private section would indicate that you are disallowing the use of objects outside of bound scope. However, in the current extensions design, all finalizers are public, regardless of the accessibility the user specifies. Therefore, the finalizer always seems to be necessary as a failsafe companion definition to a reference class just as, canonically, you generally need to pair a copy constructor with a copy assignment operator or an operator new with an operator delete. The other remaining issue, admittedly trivial, is that the code for the destructor and finalizer tends to be the same and, therefore, it is vexing to some of us to have to either duplicate the code or fret over how it should canonically be designed. Michael Vanier, a programming language professor at CalTech, suggested a ~! syntax to indicate to the compiler that it should use this code to support both destructor and finalization. I really like that idea, and perhaps the ECMA C++/CLI committee will too in some future revision! So, for a small piece of code, there were quite a few mistakes. Why? I think it’s because these issues—visibility of types, nondeterministic finalization—don’t exist in native programming and, therefore, represent leaks in our thinking about .NET. Next time we’ll look at regular expressions—in the quest to turn verbose C++/CLI into pithy Perl. Until then, may your code compile without error and execute to completion swiftly and correctly. October 05 库设计常用方法1. placement new 方法
如:pi = new (p) int; //placement new 括号里的参数是一个指针,它指向一个内存缓冲器,new操作将在这个缓冲器上分配一个对象。Placement new的返回值是这个被构造对象的地址(比如扣号中的传递参数)。placement new主要适用于:在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的; STL 标准库中 allocator 空间分配器, 就使用了这个语法。 先使用 malloc 分配内存p. 然后 在该内存内构造自己的类对象 new(p) T1(value) ; T1 是构造的类型,value 是类型的值 如果是类退出需要显示调用析构函数。pi->~pi();free(p);
2. naked function
当你的函数内部较简单,用不到太多寄存器。或者需要根据上下文决定一些寄存器变量的时候。可以用naked 函数。对于一般的函数,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。使用naked函数,我们可以自己编写干干净净的函数,特别是当你代码没有使用这些寄存器的时候。可以像内联函数一样,省略一些操作。 void __declspec(naked) MyNakedFunction() { // Naked functions must provide their own prolog. __asm { PUSH EBP MOV ESP, EBP SUB ESP, __LOCAL_SIZE } // And we must provide epilog. __asm { POP EBP RET } } 3. 指向类成员函数的指针
在MFC库中很多窗口基类中调用派生类的消息处理函数。因为windows消息众多,如果为每个消息都设立虚函数,而每个虚函数都占用4 bytes,那样很多类体积将非常庞大。而且有些地方基类设计时考虑到派生类在此可能要作些处理,又不想使用虚函数的话也可以使用指向成员函数的指针。如下面的类。如果派生类没有修改pf成员,在release中 (this->*pf)() 将被优化掉。而且使用指向成员函数的指针也不用定死函数名字. class animate { typedef void (animate::*pfun)(); public: animate() {pf=static_cast<pfun>(donothing);} void sleep() { cout<<"animate sleep"<<endl; (this->*pf)(); } void donothing(){} pfun pf; }; class fish:public animate { public: fish() {pf = static_cast<pfun> (test);} void test() { cout<<"使用基类指针调用派生类非虚函数"<<endl; } }; int _tmain(int argc, _TCHAR* argv[])
{ animate* fs = new fish;
fs->sleep(); delete fs; return 0; } 4 __declspec(novtable)
C++中没有提供类似interface这样的关键字来定义接口,但是Mircrosoft c++中提供了__declspec(novtable)来修饰一个类,来表示该类没有虚函数表,也就是虚函数都(应该)是纯虚的
声明的基类没有 vtable(虚拟函数表), 但还是有vptr指针(指向虚拟函数表)的。所以a1* a= new a1; a->hello()执行将会出错 。 而从a1派生的b1是有vtable的。所以 a1* a = new b1; a->hello() 将调用 b1 的hello函数。如果b1未定义hello函数,那么将调用 a1 函数。其实还是从b1对象的vptr指向的vtable中索引的函数地址。编译时由于b1中没有定义hello虚函数. vtable 中虚函数hello指向的是a1类hello函数地址。良好的面向对象设计应该由派生类完成实现。面向对象规则第一条就是基于接口编程。所以将a1 函数均定义为纯虚函数,a1相当于接口,由派生类b1来完成实现,这样将抽象与实现之间解耦合,更符合面向对象规范,而接口a1也可以不生成vtable 这样定义的类a1不能调用虚函数。所以a1 类应看成接口。不应生成 a1 类对象。同样程序生成时将节省a1 类 vtable 的空间。如果有大量虚函数,节省空间还是可观的 函数个数*4. 次关键字为vc 所有,其他c++编译器并不支持 class __declspec(novtable) a1 { public: a1() {} virtual void hello() { cout<<"a hello"<<endl; } }; class b1:public a1 { public : b1() {} void hello() { cout<<"b hello"<<endl; } }; |
|
|