可调用
之前我们在闭包一节中提到了“可调用对象”这个概念;当时列出了函数、函数指针、函数对象(含 Lambda 表达式)三种常见的可调用对象。但可调用对象的范围不仅仅这些。回归“可调用”一词的含义,所谓“可调用”就是指可以出现在函数调用运算符的左侧的对象。那我们还漏了什么呢?答案是成员函数。
上面的代码出现了 s.inc()
这样一个函数调用表达式。它的左侧的操作数是 s.inc
——这个东西不像是一个对象,它仿佛是成员运算符 .
的一个表达式,左侧是对象 s
,右侧是分量 inc
。但是,定义上的成员函数名字又只叫 inc
。这里就出现了一些需要讨论的问题:成员函数到底是什么?
非静态成员函数
这里主要讨论非静态成员函数。先说结论:非静态的成员函数,就是以面向对象的写法写出来的普通函数。我们可以用 C 风格的方式改写上面的代码:
成员函数 inc
被改写为全局的普通函数 S_inc
,同时添加了 S*
类型的形参 self
。调用这个“成员函数”时,需要将被调用的对象的地址传入。访问成员数据时,就通过这个 self
去间接地做。其实这里的 self
就是 C++ 中的 this
指针。成员函数相当于一个自带 this
指针形参的普通函数。比如对于类型 T
的非静态成员函数 mem
,T
类型对象 a
调用 a.mem(args...)
就相当于仿佛有一个全局函数 T::mem
,并以 T::mem(&a, args...)
的形式去调用。
所以说,非静态成员函数和非静态成员数据有本质上的不同。成员数据是每个对象都各自持有的,但成员函数某种意义上说是“共享的”“全局的”,只是在调用的时候“传入”了不同的 this
指针。
C++23 支持一种“返璞归真”的写法,显式 this 参数。
这种写法允许在成员函数的首个形参位置上添加一个 this
关键字修饰,从而表明这个形参代表了当前被调用的对象。这个形参的类型必须是 S
相关的类型(S
S&
const S&
...),形参名可以是任意的(一般都写成 self
)。在函数体中,不能使用 this
或者隐式的 this
(即 a
或 this->a
都不允许),只能通过这个 self
来访问被调用的对象,正如普通的全局函数那样。这种写法相比“面向对象的写法”更啰嗦,但更能体现非静态成员函数的“本质”。
这种写法的唯一用途是
std::forward
this
以保证值类别不变。比如:
那么话说回来,怎样在 C++ 中表示这个“本质”上的普通函数呢?这就需要用到一个冷门的语法——成员指针与成员指针运算符。
成员指针运算符
运算符 | 名称 | 作用 |
---|---|---|
.* | 成员指针运算符 | 给定某一对象,对成员指针解地址 |
->* | 指针成员指针运算符 | 给定指向某一对象的指针,对成员指针解地址 |