感知this指针 人工传递this指针技巧

来源:互联网 发布:手感好中性笔 知乎 编辑:程序博客网 时间:2024/06/06 05:10

引入

C++与C明显的不同之处就是C++对类(class)的支持,这也是C++最初被称作C with classes的原因。我们知道类是属性和行为的封装,它既包含成员变量,也包含成员函数(或称方法)。如果成员变量是私有的,外界则无法直接访问,而只能求助于该类的方法,因此外界与类成员的交流基本上被限制在调用类方法上。实际上,依据C++的编程思维,是应该鼓励这种交流方式的。在设计类的时候,应尽量将属性修饰成private,任何时候外界想要存取属性,都只能通过类对外开放的方法,这体现了C++的封装性,更严谨、安全。

与此同时,C++也尽量与C保持兼容,它在鼓励OOP编程思维的同时,仍然允许全局变量、全局函数存在。全局变量和全局函数,顾名思义就是在程序的任何位置均允许被访问或调用。它们对全局可见,且全局变量的生命周期与程序的生命周期一致:在程序启动时被创建,在程序关闭时被销毁。这与类的成员是不同的,类成员的作用域及生命周期是比较有限的。

在C++中还有一种函数比较特别,就是静态(static)的类方法。实际上,静态类方法与全局函数没有多大的差别,只不过在调用它时需显式指定其所属类的名称。比如调用类Cxxx的静态方法StaticFunc,需要写成Cxxx::StaticFunc(...);或者用Cxxx类对象调用,如Cxxx tmp; tmp.StaticFunc(...)。

既然C++中类方法分为普通方法和静态方法两种,它们有何本质区别呢?答案就在于this指针。

回想一下C++的内存区域,无论是全局函数还是类的成员函数,它们都处在代码区,一旦加载进内存就是只读的。我们通过类对象调用类方法时,实际上编译器悄悄地向类方法传递了一个this指针。有了this指针,才可以方便地访问类的成员变量。然而,静态方法却享受不到这种“优惠”,它不能轻易访问到类的成员变量,因为编译器不会给静态方法传递this指针。

接下来通过一个简单的实验来感知this指针的存在。

实验

定义类CThisPointerTracer,它含有私有的成员变量int m_nPrivateMember,公开的普通类方法ShowMember以及静态方法StaticShowMember,代码如下所示: 

// class: CThisPointerTracerclass CThisPointerTracer{private:int m_nPrivateMember;public:// constructorCThisPointerTracer() : m_nPrivateMember(0){// do nothing}// method: ShowMembervoid ShowMember(){_tprintf(_T("ShowMember was called\n"));_tprintf(_T("The private member is %d\n"), this->m_nPrivateMember);}// staic method: StaticShowMemberstatic void StaticShowMember(){_tprintf(_T("StaticShowMember was called\n"));this->ShowMember();}};

在普通类方法ShowMember中,打印私有成员变量m_nPrivateMember,这里故意显式地使用this指针来引用它,目的是与静态类方法StaticShowMember形成对比。在StaicShowMember方法中,调用this->ShowMember()是非法的,因为静态类方法内部并没有this指针。

不过我们可以手工向StaticShowMember传递一个this指针,使得静态类方法也能访问私有成员变量及成员函数。修改StaticShowMember的定义为:

// staic method: StaticShowMemberstatic void StaticShowMember(CThisPointerTracer* pThis){assert( pThis != NULL );_tprintf(_T("StaticShowMember was called\n"));pThis->ShowMember();}

我们手工传递pThis指针后,调用pThis->ShowMember()就是合法的了。为了防止pThis指针为空,使用assert()函数进行检测预防。

定义main函数为:

int _tmain(int argc, _TCHAR* argv[]){CThisPointerTracer tpt;CThisPointerTracer::StaticShowMember(&tpt);return 0;}

运行结果如我们所期待的那样,如下所示:

StaticShowMember was calledShowMember was calledThe private member is 0请按任意键继续. . .

小结

this指针是普通类方法与静态类方法的本质区别。在普通类方法中可以直接使用this指针,是因为编译器悄悄向普通类方法传递了this指针。然而,在静态类方法中不能直接使用this指针,因为编译器不会为它传递this指针。之所以这样做是为了兼顾作用域与生命周期两方面的因素:它既属于某个特定的类,又不与任何具体的类对象绑定。

在特殊情况下,我们可以手工向静态类方法传递this指针,但必须确保该指针是有效的。实际上,ATL在封装窗口处理函数就使用了这样的技巧。▲