5.9 类成员指针
简介
成员指针是指可以指向类的非静态成员的指针。一般情况下,指针指向一个对象,但是成员指针指示的是类的成员,而非类的对象。类的静态成员不属于任何对象,因此无须特殊的指向静态成员的指针,指向静态成员的指针和普通指针没有任何区别。
Tips:成员指针的类型囊括了类的类型以及成员的类型。当初始化一个这样的指针时,我们令其指向类的某个成员,但是不指定该成员所属的对象;直到使用成员指针时,才给提供成员所属的对象。
为了解释成员指针的原理,我们使用该Screen
类:
class Screen {
public:
typedef std::string::size_type pos;
char get_cursor() const { return contents[cursor]; }
char get() const;
char get(pos ht, pos wd) const;
private:
std::string contents;
pos cursor;
pos height, width;
};
数据成员指针
与普通指针不同的是,成员指针还必须包含成员所属的类。因此,我们必须在*
之前添加classname::
以表示当前定义的指针可以指向classname
的成员,例如:
// pdata可以指向一个常量(非常量)Screen对象的string成员
// 将pdata声明为"一个指向Screen类的const string成员的指针"
const string Screen::*pdata;
当我们初始化一个成员指针(或者向它赋值)时,需要指定它所指的成员。例如我们可以令pdata
指向某个非特定Screen
对象的contents
成员:
// 初始化
pdata = &Screen::contents;
在C++11新标准中声明成员指针最简单的方法是使用auto
或者decltype
:
// C++11新标准的初始化
auto pdata = &Screen::contents;
1. 使用数据成员指针
当我们初始化一个成员指针或者为成员指针赋值时,该指针并没有指向任何数据。成员指针指定了成员而非该成员所属的对象,只有当解引用成员指针时我们才提供对象的信息。
我们可以通过.*
和->*
两个运算符解引用指针以获得该对象的成员:
Screen myScreen, *pScreen = &myScreen;
// .*解引用pdata以获得myScreen对象的contents成员
auto s = myScreen.*pdata;
// ->*解引用pdata以获得pScreen所指对象的contents成员
s = pScreen->*pdata;
2. 返回数据成员指针的函数
Screen
的contents
成员是私有的,因此之前对于pdata
的使用必须位于Screen
类的成员或友元内部,否则程序将发生错误。如果像Screen
这样的类希望我们可以访问它的contents
成员,最好定义一个函数:
class Screen {
public:
// data是一个静态成员, 返回一个成员指针
static const std::string Screen::*data()
{ return &Screen::contents; }
}
// 我们调用data函数时, 将得到一个成员指针
// data()返回一个指向Screen类的contents成员的指针
const string Screen::*pdata = Screen::data();
// pdata指向Screen类的成员而非实际数据, 要想使用pdata必须把它绑定到Screen类型的对象上
auto s = myScreen.*pdata;
成员函数指针
我们也可以定义指向类的成员函数的指针:
// pmf是一个指针, 它可以指向Screenn的某个常量成员函数
// 前提是该函数不接受任何实参, 并且返回一个char
auto pmf = &Screen::get_cursor;
- 指向成员函数的指针也需要指定目标函数的返回类型和形参列表
- 如果成员函数是
const
成员或引用成员,我们必须将const
限定符或者引用限定符包含进来 - 如果成员存在重载的问题,那么我们必须显式地声明函数类型以明确指出来我们想要使用的是哪个函数
// 例如我们可以声明一个指针, 令其指向含有两个形参的get:
char (Screen::*pmf2)(Screen::pos, Screen::pos) const;
// 必须加取地址符&, 在成员函数和指针之间不存在自动转换规则
pmf2 = &Screen::get;
1. 使用成员函数指针
Screen myScreen, *pScreen = &myScreen;
// 通过myScreen所指的对象调用pmf所指的函数
char c1 = (pScreen->*pmf)();
// 通过myScreen对象将实参0, 0 传给含有两个形参的get函数
char c2 = (myScreen.*pmf2)(0, 0);
2. 使用成员指针的类型别名
使用类型别名或者typedef
可以让成员指针更容易理解,例如下面的类型别名将Action
定义为两个参数get
函数的同义词:
// Action是一种可以指向Screen成员函数的指针, 它接收两个pos实参, 返回一个char
using Action = char (Screen::*)(Screen::pos, Screen::pos) const;
通过使用Action
,我们可以简化指向get
的指针定义:
// get指向Screen的get成员
Action get = &Screen::get;
我们可以将指向成员函数的指针作为某个函数的返回类型或者形参类型:
// action接受一个Screen的引用和一个指向Screen成员函数的指针
Screen& action(Screen&, Action = &Screen::get);
Screen myScreen;
// 等价调用
action(myScreen); // 使用默认实参
action(myScreen, get); // 使用我们之前定义的变量get
action(myScreen, &Screen::get); // 显式地传入地址
3. 成员指针函数表
对于普通函数指针和指向成员函数的指针来说,一种常见的用法是将其存入一个函数表当中。如果一个类含有几个相同类型的成员,则这样一张表可以帮助我们从这些成员中选择一个。假定Screen
类中含有几个成员,每个函数负责将光标向指定的方向移动:
class Screen {
public:
// 其他接口和实现成员与之前一致
// 这几个函数共同点: 不接受任何参数, 并且返回值是发生光标移动的Screen的引用
Screen& home();
Screen& froward();
Screen& back();
Screen& up();
Screen& down();
}
我们希望定义一个move
函数,使其可以调用上面任意一个函数并执行对应的操作。为了支持这个新函数,我们将在Screen
中添加一个静态成员,该成员是指向光标移动函数的指针的数组:
class Screen {
public:
// Action是一个指针, 可以用任意一个光标移动函数对其赋值
using Action = Screen& (Screen::*)();
// 指定具体要移动的方向
enum Directions { HOME, FORWARD, BACK, UP, DOWN };
Screen& move(Directions);
private:
static Action Menu[]; // 函数表
};
Screen& Screen::move(Directions cm) {
// 运行this对象中索引值为cm的元素
// Menu[cm]指向一个成员函数
return (this->*Menu[cm])();
}
Screen::Action Screen::Menu[] = {
&Screen::home,
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
};
当我们调用move
函数时,给它传入一个表示光标移动方向的枚举成员:
Screen myScreen;
myScreen.move(Screen::HOME); // 调用myScreen.home
myScreen.move(Screen::DOWN); // 调用myScreen.down
将成员函数用作可调用对象
要想通过有一个指向成员函数的指针进行函数调用,必须首先利用.*
或者->*
运算符将该指针绑定到特定的对象上。因此与普通的函数指针不同,成员指针不是一个可调用对象,这样的指针不支持函数调用运算符。
因为成员指针不是可调用对象,因此我们不能直接将一个指向成员函数的指针传递给算法。比如我们想在一个string
的vector
中找到第一个空string
,显然不能这么写:
// fp指向string的empty函数
auto fp = &string::empty;
// 错误, 必须使用.*或者->*调用成员指针
find_if(svec.begin(), svec.end(), fp);
// 在find_if内部试图执行如下代码, 但是要想通过成员指针调用函数, 必须使用该->*运算符, 所以失败
if (fp(*it))
1. 使用fuction生成一个可调用对象
vector<string> svec;
function<bool (const string&)> fcn = &string::empty;
find_if(svec.begin(), svec.end(), fcn);
vector<string*> pvec;
function<bool (const string*)> fp = &string::empty;
find_if(pvec.begin(), pvec.end(), fp);
2. 使用mem_fn生成一个可调用对象
find_if(svec.begin(), svec.end(), mem_fn(&string::empty));
// mem_fn生成的对象可以通过对象调用, 也可以通过指针调用
auto f = mem_fn(&string::empty); // f接收一个string或者一个string*
f(*svec.begin()); // 正确: 传入一个string对象, f使用.*调用empty
f(&svec[0]); // 正确: 传入一个string指针, f使用->*调用empty
3. 使用bind生成一个可调用对象
auto it = find_if(svec.begin(), svec.end(), bing(&string::empty, _1));
// bind生成的可调用对象第一个实参既可以是string的指针, 也可以是string的引用
auto f = bind(&string::empty, _1);
f(*svec.begin());
f(&svec[0]);