在C++中this是什么

本文部分代码仅作为示例,并不能通过编译。

this是什么?

在C++中,假设有一个class NN的定义如下

1
2
3
4
5
6
7
8
9
10
11
12
class N {
public:
N(int num) : num(num) {}
int get() {
return num;
}
int increase(int num) {
return this->num + num;
}
private:
int num;
};

假设nN类型的一个变量

1
N n(1);

当我们有调用成员函数increase的时候,会发生什么呢?

1
n.increase(2);

此时会发生重载决议,寻找一个函数increase(N, int),注意这里多出了一个隐藏的N类型的参数,它代表了调用这个成员函数的对象。所以说在成员函数increase中存在一个N类型的形参,我们可以通过这个形参找到这个对象,不过此时这个形参的类型是N,可N类型作为形参会被认为是一个新的对象(因为C++通过拷贝传递参数)。看上去编译器做了一件事,即创建了一个N*类型的变量叫做this。就像下面这样。

1
2
3
4
int increase(N n, int num) {
auto* this = &n;
return this->num + num;
}

但是这里

this的一些性质

  • this永远指向当前对象,无论该对象是否为另一对象的子对象。
  • this是一个N*类型的纯右值表达式,我们并不能假设this是一个变量
  • this可以有任意的cv限定符,也可以有引用修饰符,与重载决议时的添加的隐式对象形参拥有的相同。
  • 在模板中,this可以让名字在实例化的时候进行绑定

this的修饰(限定)符

因为this会拥有与其隐式对象形参相同的cv限定符和引用修饰符,这里先讨论这个隐式形参对象。

cv限定符

成员函数上的cv修饰符要求调用该成员函数的对象拥有不少于它的限定符,即对象可以向该隐式对象形参进行隐式转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
class T {
public:
void c_foo() const;
void foo();
};
int main() {
T t1;
t1.c_foo(); // error
t1.foo(); // OK
const T t2;
t2.c_foo(); // OK
t2.foo(); // OK
}

引用修饰符

成员函数上的引用修饰符可以要求对象表达式的值类别是左值(通过无仅拥有const限定符的左值引用)或右值(通过任意&&)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class T {
public:
void foo() & {
cout << "foo &" << endl;
}
// don't insert no & version and has & version in same class
// void foo() {
// cout << "foo" << endl;
// }
void foo() && {
cout << "foo &&" << endl;
}
void foo() const & {
cout << "foo const &" << endl;
}
};

int main() {
T t;
t.foo(); // foo &
T().foo(); // foo &&
const T c_t;
c_t.foo(); // foo const &
}

关于引用可以可见过往的博客在C++中引用是什么?

若仅考虑引用修饰符,这里会有三种写法

  • 没有任何引用修饰符 匹配左值和右值
  • 拥有&(左值引用)修饰符 仅匹配左值
  • 拥有&&(右值引用)修饰符 仅匹配右值

其中没有引用修饰符的版本与任一拥有引用修饰符的版本互斥,因此要么都写引用修饰符,要么都不写引用修饰符。

关于其中的隐藏对象参数的类型

  • 对于拥有&修饰符的版本,隐藏对象参数用过&(左值引用)的方式传递
  • 对于拥有&&修饰符的版本,隐藏对象参数用过&&(右值引用)的方式传递
  • 对于没有任何引用修饰符的版本,隐藏对象参数通过既非左值引用亦非右值引用的另一种引用传递。在函数内的行为上和拥有&的版本相同

Deducing this

当成员函数需要根据调用成员函数的对象表达式的值类别的不同去接收不同版本的参数时,往往会重复编写同样地函数。在Effective C++的条款3中提供了非常经典的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TextBlock {
public:
char const& operator[](size_t position) const {
// ...
return text[position];
}

char& operator[](size_t position) {
return const_cast<char&>(
const_cast<TextBlock const&>(*this)[position]
);
}
// ...
};

在有了deducing this之后,我们可以通过使用显式this注释参数的方式实现。

先看这个解决方案

1
2
3
4
5
6
7
8
9
class TextBlock {
public:
template <typename Self>
like_t<Self, char>& operator[](this Self&& self, size_t position) {
// ...
return self.text[position];
}
// ...
};

首先,this注释会出现在非静态成员函数的第一个形参前,当this注释出现时,this指针将不可用,函数编写者应该通过第一个参数访问这个调用函数的对象,相当于编译器不再生成this指针,就像这个成员函数的最后一条语句通过self而不是this来访问对象。特别地,如果函数的第一个参数没有任何的引用修饰符,那么将会按值传递,这是以往从未出现过的。

this注释允许我们通过模板的形式自动生成对象参数的类型,而不是必须在函数末尾直接确定。

而如何确定返回char&还是char const&的关键在于新的模板like_tlike_t接收两个类型参数,并将第一个参数的cv限定符和引用修饰符加在第二个参数上。这里我们通过like_t<Self, char>得到Self的cv限定符与引用修饰符,并加在char之上。然后在其后面加上&进行引用折叠得到左值引用。

自此我们不再需要重复很多次相同的函数或者写很多的const_cast/static_cast

关于deducing this的更多内容,见将来的博客。

文章目录
  1. 1. this是什么?
  2. 2. this的一些性质
  3. 3. this的修饰(限定)符
    1. 3.1. cv限定符
    2. 3.2. 引用修饰符
  4. 4. Deducing this
|