目录
- 一 MSDN中使用说明
- 1.1 语法
- 1.2 备注
- 二 一个使用案例
一 MSDN中使用说明
1.1 语法
expression .* expression //直接成员解除引用运算符
expression –>* expression //间接成员解除引用运算符
1.2 备注
C++中指向成员的指针运算符(.* 和 ->*)返回表达式左侧所指定的对象的特定类成员的值。 右侧必须指定该类的成员。 下面的示例演示如何使用这些运算符:
// expre_Expressions_with_Pointer_Member_Operators.cpp
// compile with: /EHsc
#include <iostream>using namespace std;class Testpm {
public:void m_func1() { cout << "m_func1\n"; }int m_num;
};// Define derived types pmfn and pmd.
// These types are pointers to members m_func1() and
// m_num, respectively.
void (Testpm::*pmfn)() = &Testpm::m_func1;
int Testpm::*pmd = &Testpm::m_num;int main() {Testpm ATestpm;Testpm *pTestpm = new Testpm;// Access the member function(ATestpm.*pmfn)();(pTestpm->*pmfn)(); // Parentheses required since * binds// less tightly than the function call.// Access the member dataATestpm.*pmd = 1;pTestpm->*pmd = 2;cout << ATestpm.*pmd << endl<< pTestpm->*pmd << endl;delete pTestpm;
}
输出为:
m_func1
m_func1
1
2
在前面的示例中,指向成员的指针 pmfn 用于调用成员函数 m_func1。 另一个指向成员的指针 pmd 用于访问 m_num 成员。
二元运算符 .* 将其第一操作数(必须是类类型的对象)与其第二操作数(必须是指向成员的指针类型)组合在一起。
二元运算符 ->* 将其第一操作数(必须是指向类类型的对象的指针)与其第二操作数(必须是指向成员的指针类型)组合在一起。
在包含 .* 运算符的表达式中,第一个操作数必须是第二个操作数中指定的成员的类类型,并且可由该操作数访问,或者必须是明确派生自该类并可供该类访问的可访问类型。
在包含 –>* 运算符的表达式中,第一个操作数的类型必须是第二个操作数中指定的类型的“指向类类型的指针”类型,或者它必须是明确派生自该类的类型。
下面的示例说明了指向成员的指针运算符的第一个操作数可以是第二个操作数指定的类型的派生类的对象。
// expre_Expressions_with_Pointer_Member_Operators2.cpp
// C2440 expected
class BaseClass {
public:BaseClass(); // Base class constructor.void Func1();
};// Declare a pointer to member function Func1.
void (BaseClass::*pmfnFunc1)() = &BaseClass::Func1;class Derived : public BaseClass {
public:Derived(); // Derived class constructor.void Func2();
};// Declare a pointer to member function Func2.
void (Derived::*pmfnFunc2)() = &Derived::Func2;int main() {BaseClass ABase;Derived ADerived;(ABase.*pmfnFunc1)(); // OK: defined for BaseClass.(ABase.*pmfnFunc2)(); // Error: cannot use base class to// access pointers to members of// derived classes.(ADerived.*pmfnFunc1)(); // OK: Derived is unambiguously// derived from BaseClass.(ADerived.*pmfnFunc2)(); // OK: defined for Derived.
}
指向成员的指针运算符 .* 或 ->* 的结果是在指向成员的指针的声明中指定的类型的对象或函数。 因此,在前面的示例中,表达式 ADerived.*pmfnFunc1() 的结果是指向返回 void 的函数的指针。 如果第二操作数是左值,则此结果为左值。
二 一个使用案例
以上MSDN的说明相对简单,并没有展示指向成员的指针运算符的真实威力。那么,.* 和 ->* 运算符到底有什么用?为什么不直接用 直接成员\间接成员 运算符呢?我理解是为了特定情况下,规避虚函数表的使用。
我们知道,基类指针想要调用子类成员函数,一般需要将该成员函数定义为虚函数,通过在类中维护虚函数表,从而实现基类指针调用子类方法的效果。但是虚函数需要在每个类中维护一个虚函数表,大大增加了程序了空间复杂度。
在某些特定的使用情境下,例如在MFC中,有众多的消息,如果在基类中将所有消息的响应函数都定义为虚函数,那么在每个派生类中都需要维护一个虚函数表,那么整个MFC程序的空间复杂度陡增。开发MFC框架的天才们并没有这样做,也就是并没有在基类中将消息响应函数定义为虚函数。而是利用了.* 和 ->* 运算符实现了类似虚函数的多态性。
我们先看下面一个例程。
#include<iostream>
using namespace std;class CBase
{
public:void BasePrintMsg() {cout << "In Base class" << endl;}
};class CDerive :public CBase
{
public:CDerive() :m_iDerive(3) {}int GetInt(int m) {return m_iDerive * m;}double GetDouble(int m, double d) {return m * d;}
private:int m_iDerive;
};typedef void (CBase::* pBaseVoidFun)();union UMapFuns
{//请注意,以下三个成员都是基类的成员函数的函数指针pBaseVoidFun pfn;double (CBase::*pfn_double)(int m, double d);int (CBase::*pfn_int)(int m);//请注意,下面是派生类的函数指针double (CDerive::*pfn_double_derive)(int m, double d);
};int main()
{CDerive cDeriveObj;CBase* pBase = &cDeriveObj;UMapFuns uMapFun;//调用成功,关键要理解uMapFun.pfn_int的地址实际上是子类成员函数的地址,子类对象//调用成员解除引用运算符时,不过是将子类对象的地址压栈,让子类成员函数能够根据这个//地址找到类的其他成员。uMapFun.pfn = (pBaseVoidFun)&CDerive::GetInt;int i1 = (cDeriveObj.*(uMapFun.pfn_int))(3); //调用成功,用基类指针调用子类函数。关键要理解uMapFun.pfn_int的地址实际上是子类//成员函数的地址,基类指针此时的地址就是子类对象的地址,该地址压栈后,//子类成员函数根据这个地址找到的类的其他成员都是正确的。int i2 = (pBase->*(uMapFun.pfn_int))(4);uMapFun.pfn = (pBaseVoidFun)&CDerive::GetDouble;double d = (pBase->*(uMapFun.pfn_double))(3, 4.0); //调用成功,用基类指针调用子类函数//调用失败,因为->*或者.*的第一个操作符必须是第二个操作符所属类或者其派生类的指针(对象)//double d2 = (pBase->*(uMapFun.pfn_double_derive))(3, 4.0);return 0;
}
请注意思考上面的这行代码
int i2 = (pBase->*(uMapFun.pfn_int))(4);
这是个令人动容的时刻,从代码看,我们竟然用基类指针调用了派生类的成员函数!为什么可以这样做呢?
首先,派生类的成员函数指针可以显式转为基类的成员函数指针(哪怕基类中并没有这个成员函数,我们仅仅是定义了这样一种函数指针类型,如同联合体UMapFuns所作的一样)。其次,基类指针(请注意指针此时指向派生类对象)使用间接成员解除引用运算符时,实际上流程会转到这个函数指针所指向的地址(也就是子类成员函数中),而在调用这个这个子类对象的成员函数时,会把基类的this指针同时压栈;而这个this指针指向的地址实际上不仅包含基类对象,还包含子类对象!子类成员函数根据这个地址可以正确找到相应的类成员。
在MFC中,正是利用了上述原理,将子类实现的所有消息响应函数集合为一个列表,接收消息以后,在子类中查找是否有对应的消息响应函数;一旦找到,就通过基类指针调用(当然是通过.* 和 ->*)这个子类的消息响应函数,从而实现由下向上(从子类到基类的方向)处理消息的目的。