说实话,我第一次认真看这个C++问题,是在给组里新人做代码评审复盘的时候。当时我问了个很常见的面试题:“空类编译器默认生成哪些函数?”对方回答得很流畅:默认构造、析构、拷贝构造、拷贝赋值。我又追了一句:“还有呢?”他愣了半天,最后才从记忆深处掏出一个答案——还有两个取地址函数。
这个答案本身不算错,但大多数人说的时候,只是把它当成需要硬背的“八股文”,很少去想最关键的那句话:为什么C++编译器要默认生成两个取地址函数?这两个operator&到底藏在哪里?它们和普通成员函数到底是不是一回事?
这篇文章我就把这个话题彻底拆开。会先讲清楚“两个取地址函数”在源码层面的样子,再把重载决议中的内建候选机制讲透,最后用几个实验证明编译器到底做了什么。内容不适合纯零基础,但只要碰到过指针、会写简单class,应该都能跟上。
1. 那道经典空类题的第六个答案:编译器在背后默认放好了两个取地址函数
1.1 常见的默认函数清单里,两个&总被忽略
很多C++教程在讲“编译器默认生成”时,通常列这几样:默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符。到了C++11之后还要补上移动构造和移动赋值。
但国内流传很广的另一版本面试整理里,还会在列表尾部接上两句:
cpp复制Demo* operator&() { return this; }
const Demo* operator&() const { return this; }
也就是说,编译器为每个类默认准备的非静态成员函数里,除了那几个特殊成员函数,还有两个一元取地址运算符:一个处理非const对象,另一个处理const对象。
你平时写代码时,并不需要手写这样的函数,但依然可以这样做:
cpp复制struct Demo {
int value;
};
int main() {
Demo d;
Demo* pd1 = &d;
const Demo cd{};
const Demo* pd2 = &cd;
}
这个例子编译完全没问题。&d得到的类型是Demo*,&cd得到的类型是const Demo*。如果不考虑编译器在背后兜底,这两行代码是没法解释的。
1.2 这两个函数在源码里的“标准长相”
为了让
