对牛弹吉他 2004-7-30 18:21
C++小花园
<!-- CETagParser ~size=4
<font size=4> 这是一个小小的花园,我就是园丁。我在这里种植些小小的C++之花。每一株都有它自己的色彩,每一株都有特有的芬芳。我小心的栽培着,与朋友们分享着美丽。
<!-- CETagParser ~color=#FF7F50
<font color="#FF7F50">第一朵花:NULL的秘密<!-- CETagParser ~/color
</font>
在C与C++中对NULL的定义是不同的:
<!-- CETagParser ~code
<br><table cellpadding=0 cellspacing=0 border=0 WIDTH=94% bgcolor='#000066' align=center><tr><td><table width=100% cellpadding=5 cellspacing=1[/img]<TR><TD BGCOLOR='#f4f4f4'>#define NULL 0; // C++中对NULL的定义
#define NULL? ((void*)0) // C 中NULL是这样定义的<!-- CETagParser ~/code
</td></tr></table></td></tr></table><br>
为什么在这两种语言中会有不同的定义呢?
在C++中指针是强类型的,而在C中则不是。因此,void*在C++中只能通过显式转换来转换为其它类型的指针。如果C++中的NULL按照C中的定义,那么下面的语句
char * p = NULL;
实际上会扩展为:
char * p = (void*) 0; // 编译器将产生错误: incompatible pointer types
这就是两种语言中对NULL作不同定义的原因了。
<!-- CETagParser ~color=#FF7F50
<font color="#FF7F50">第二朵花:inline vs. __forceinline<!-- CETagParser ~/color
</font>
MS Visual C++, 以及其它几种编译器,提供了一个非标准的用于控制函数内嵌(inline)的关键字,作为对标准关键字inline的补充。为什么要添加这个非标准关键字呢?先让我们来看看inline的一些局限,决定一个声明为inline的函数是否真的进行嵌入,完全取决于编译器的判断。因此inline只是一个建议,在一些情况下,比如在一些内嵌函数中包含有循环或是这个函数体太大了,那么即使这个函数声明为inline,编译器也将拒绝这个函数的嵌入。
与此相反,非标准关键字__forceinline 将忽略编译器的判断并强迫编译器去嵌入一个它本该拒绝嵌入的函数。我不太肯定使用这个关键字的意义,它可能会使可执行文件变得臃肿并降低cache的命中率。幸运的是,在一些极端条件下,编译器可能不接受__forceinline的任何请求。所以,一般情况下最好是使用标准的inline,inline是可移植的并且让编译器去做出“正确的选择”。
__forceinline 只应在下列条件全为真的情况下使用:inline不被编译器接受;你的代码不需要向其它平台进行移植;并且你能肯定嵌入这个函数会提高性能。
<!-- CETagParser ~color=#FF7F50
<font color="#FF7F50">第三朵花:类成员指针<!-- CETagParser ~/color
</font>
我常常听到很多有关类成员指针的问题:“我使用VC,为什么我不能使用一个类方法的指针呢?编译错误信息是:
Cannot convert parameter 2 from 'long (unsigned long)'to'long (__cdecl *)(unsigned long)'我该怎么办?”下面的代码是解决方法之一:
<!-- CETagParser ~code
<br><table cellpadding=0 cellspacing=0 border=0 WIDTH=94% bgcolor='#000066' align=center><tr><td><table width=100% cellpadding=5 cellspacing=1[/img]<TR><TD BGCOLOR='#f4f4f4'>//in the header
class CKernel:
{
long (*lpFunc)(DWORD);
long OLESendTC( DWORD dwInfo );
}
//in the cpp File
BOOL CKernel::Init()
{
lpFunc = OLESendTC;
return TRUE;
}<!-- CETagParser ~/code
</td></tr></table></td></tr></table><br>
所有的类方法都有一个隐藏参数,一个指向类对象的指针。C++使用这个指针来寻找此方法可能会使用的类中任何数据的位置。如果你尝试并使用一个标准的函数指针来调用一个类的方法,C++将不能传递这个隐藏参数并引起问题。
为了解决这个问题并改善类型安全,C++添加了三个新的操作符,::*,.*,与->*,它们既可指向成员函数也可指向成员变量。
<!-- CETagParser ~code
<br><table cellpadding=0 cellspacing=0 border=0 WIDTH=94% bgcolor='#000066' align=center><tr><td><table width=100% cellpadding=5 cellspacing=1[/img]<TR><TD BGCOLOR='#f4f4f4'>class CTest
{
public:
BOOL Init();
long OLESendTC(DWORD dwInfo);
};
long (CTest::*lpFunc)(DWORD dwInfo) = &CTest::OLESendTC;
int main()
{
CTest test;
(test.*lpFunc)(0);
return 0;
}
long CTest::OLESendTC(DWORD dwInfo)
{
cout << "IN OLESENDTC\n";
return 0;
}<!-- CETagParser ~/code
</td></tr></table></td></tr></table><br>
这个例子展示了这些运算符的一种用法。代码使用::*去声明lpFunc为类CTest成员函数的指针。注意最好在运行时给这个指针赋值,简单的方法是在声明时初始化它。在main函数中使用.*运行符来调用lpFunc所指向的方法。如果test在这是一个指针,你可以使用->*来代替。
C++有很多类似的隐藏特性,我们要在学习中对此多加注意。<!-- CETagParser ~/size
</font>
B-BOY 2004-8-1 12:21
Re:C++小花园
强的,其实这些小技巧,在thinking in c++上或多或少都有提到过,Thinking in C++绝对是一本好书,看了两遍,我都觉得自己还是有很多困惑,绝对值得多读几遍,当然啦,中文版是烂了点 [img]http://bbs.tongji.net/images/smiles/laugh.gif[/img] 不过琢磨琢磨还是能有收获的。另外问一下楼主,这个是转自哪里的啊??
海王星 2004-8-10 03:48
Re:C++小花园
今天居然看到一本书上说:"所有类中的函数是不能取地址的,除非用静态的方法"................
我在路上 2004-8-10 22:25
Re:C++小花园
[quote]
今天居然看到一本书上说:"所有类中的函数是不能取地址的,除非用静态的方法".............[/quote]
<!-- CETagParser ~code
<br><table cellpadding=0 cellspacing=0 border=0 WIDTH=94% bgcolor='#000066' align=center><tr><td><table width=100% cellpadding=5 cellspacing=1[/img]<TR><TD BGCOLOR='#f4f4f4'>A a;
void (A::*pf)() = A::f;
(a.*pf)();<!-- CETagParser ~/code
</td></tr></table></td></tr></table><br>
那个算是去成员函数地址吗?
OwnWaterloo 2008-10-8 03:21
[quote]原帖由 [i]B-BOY[/i] 于 2004-8-1 12:21 发表 [url=http://bbs.tongji.net/redirect.php?goto=findpost&pid=3113174&ptid=174277][img]http://bbs.tongji.net/images/common/back.gif[/img][/url]
Thinking in C++绝对是一本好书[/quote]
__forceinline 绝对没提过。
Thinking in C++ 也绝对是一本烂书。
看看 void main() 就知道。
OwnWaterloo 2008-10-8 04:06
回第1朵花:
在老C的编译器 <stddef.h> 或者其他需要的地方
#define NULL ((void*)0)
甚至
#define NULL ((char*)0)
在C++编译器 <cstddef>
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
所以使用NULL一般不会造成太大问题
在C++里面, 推荐使用 0
如果觉得不够清晰
namespace {
const int [color=blue]nullptr[/color] = 0;
}
或者EffC++里面有个
namespace {
const struct NullClass
template< typename T >
operator T*() const { return 0; }
// 其他保护性代码
} [color=blue]nullptr[/color];
}
为什么要取[color=red][b]nullptr[/b][/color] 这个名字?
因为[color=red][b]C++0x[/b][/color]中将会引进这个关键字。 现在使用, 可以算是一种[b]向前[/b][b]兼容[/b] ……
OwnWaterloo 2008-10-8 04:08
回第2朵花:
__forceinline 印象中我只在2个地方用过
__forceinline uint64_t timestamp();
__forceinline void KBC_Wait4IBE();
但是对于debug编译, 加不加都不会被inline , 所以还是用Macro弄比较保险。
还有一个必须inline的地放, 如果__forceinline 都被拒, 这函数就无法实现:
alloca
OwnWaterloo 2008-10-8 04:19
回第3朵花:
还是没能解决问题。
当“[b]我使用VC,为什么我不能使用一个类方法的指针呢[/b]?” 这种问题出现的时候,他是[b][color=red]真正[/color][/b]的需要这么做:
把 [b]pointer to[/b] [color=red][b]non-static member function[/b][/color] 当作 [b]pointer to[/b] [color=red][b]plain-c-function[/b][/color] 传递
而不是使用 [color=red]([b]obj.*pmf)();[/b][/color] 或者 [color=red]([b]pobj->*pmf)();[/b][/color]
真正解决的问题办法是:
一.如果需要传递的函数指针带有类似 void* user_data 的参数
例:
unsigned __stdcall thread_proc(void* param);
方法1: 可移植的方法
class Thread {
public:
static unsigned __stdcall Proc(void* param) {
Thread* this[color=red]_[/color] = static_cast<Thread*>( param );
[b]this[color=red]_[/color][/b]->data_;
[b]this[color=red]_[/color][/b]->MF();
//
}
///
};
int main() {
Thread t;
_beginthreadex(0,0,&Thead:: Proc,&t, ... );
}
方法2: 不可移植的方法
class Thread {
public:
unsigned __stdcall Proc( [b][color=red]void[/color][/b] ) {
// this is a non-static member function
data_;
MF();
//
}
//
};
int main() {
Thread t;
union {
unsigned ( __stdcall Thread::* src )(void) ;
unsigned ( __stdcall * dst) (void* );
} caster = { &Thread:: Proc };
_beginthreadex(0,0,caster.dst,&t, ... );
///
}
二.如果没有user_data, 但是有一些别的可以作为[color=red][b]identify[/b][/color]参数
例:
LRESUTL WndProc(HWND [b]identify[/b],UINT msg, WPARAM, LPARAM);
可以按照上面的方案1, 并维护 [b]identify[/b] 和 [b]this[/b] 的[color=red]mapping table[/color]
使用这种方案的例子: MFC
三.如果没有以上2个条件, 但是也没有object的概念, 可以使用全局变量
例:
LRESULT CallNextHookEx(HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam );
一般来说(也仅仅是一般来说),一个dll只会某个进程安装一个hook。 全局变量可以凑合着用……
也就是说, 退化到无object概念的地步。
使用这种方案的例子: web上搜 hook , 一大堆。
四.如果啥条件都没有 ……
例:
LRESULT CallWindowProc( WNDPROC [i]lpPrevWndFunc[/i], HWND [i]hWnd[/i], UINT [i]Msg[/i], WPARAM [i]wParam[/i], LPARAM [i]lParam [/i]);
(以应用程序开发来讲, Wnd的USER_DATA可以自行挪用, 但是对于[color=red]库开发[/color]来说, 是不可以的, 就真的是最囧迫的情况了 …… )
只有使用[b][color=red]Thunk[/color][/b], 别无他法。 注意 Thunk is machine code dependency.
当然Thunk也可以对付上面的[color=red]所有[/color]情况, 而且效率是[color=red]最优秀[/color]的。
所有情况是指: 需要将 pointer to non-static member function 转化成 pointer to plain-c-function 的情况。
使用这种方案的例子: ATL( WTL当然也是 )
但是也仅仅是用作处理WndProc, SubClassing就没做了。
如何把Thunk做得更generic一点, 可以参考:
[url=http://www.codeproject.com/KB/cpp/GenericThunks.aspx]http://www.codeproject.com/KB/cpp/GenericThunks.aspx[/url] EN
[url=http://www.vckbase.com/document/viewdoc/?id=1821]http://www.vckbase.com/document/viewdoc/?id=1821[/url] CHS
这个库我还在改, 方向是 compiler-independency
但是, 又会严重降低效率。 没啥动力改……
反正代码也不多 换了平台重写就是 为了不同平台兼容牺牲效率不太划算……
[[i] 本帖最后由 OwnWaterloo 于 2008-10-8 04:31 编辑 [/i]]
omale 2008-10-13 15:30
自从05年听Bjarne Stroustrup说C++ 0x的特性后。
我怎么老觉得c++0x要难产,要变c++1x或者c++0A, 0B, 0C……