这是我之前的帖子
的后续内容参考非静态成员函数
下
常量、易失性和引用限定成员函数
非静态成员函数可以在没有引用限定符的情况下声明,... 在重载决策期间,非静态 cv 限定的成员函数 X 类的处理方式如下:
无引用限定符:隐式对象参数的类型为左值 引用 cv 限定的 X,并且还允许绑定右值 隐含的对象参数
为了进一步探索这一点,我对上面链接中提供的源代码进行了实验,如下所示:
#include <utility>
#include <iostream>
using std::move;
using std::cout;
using std::endl;
struct S {
void f() {cout << "no ref-qualifier: the implicit object parameter has type lvalue reference to cv-qualified S and is additionally allowed to bind rvalue implied object argument\n"; }
//if only the below method signature were to be enabled,
//the invocations using rvalue implicit object would fail
//to compile with the error [-fpermissive]
//void f() & {cout << "lvalue\n"; }
//if only the below method signature were to be enabled,
//the invocation using lvalue implicit object would fail
//to complile with the error [-fpermissive]
//void f() && {cout << "rvalue\n"; }
};
int main (void){
S s;
s.f(); // prints "lvalue"
move(s).f(); // prints "rvalue"
S().f(); // prints "rvalue"
return 0;
}
我已经在基于引用限定符的每个非静态成员函数重载之上提供了相关注释,突出显示了如果仅启用该特定重载就会出现的编译问题,鉴于
main()
中的源代码。
我的问题是,为了让非引用限定的非静态成员函数能够不知道调用它的对象的隐式类型,幕后发生了什么?编译器是否介入适当的重载?
欣赏您的想法。
Const 和 ref 限定成员函数的功能与非 const/ref 成员函数没有什么不同。这些标记主要是为了阻止程序员滥用东西。
不,该对象是 const 因为你不应该更改它,所以 你不能在上面调用变异函数!
- 编译器对程序员大喊大叫。
如果你有一个函数
void g() const
,那么它不会突然编译为不同的指令,因为你从中删除了 const
- 这只是意味着编译器停止检查你是否在函数体中改变了 this
。
嗯,主要是......事实变得有点复杂,通过维护各种不变量,这些关键字有时还允许程序员(或编译器)做一般情况下不允许的事情。例如,如果我知道你给了我一个右值,那么我就可以窃取它的内部结构,而不需要复制它们。
无论如何,为了说明这一点,我通过 godbolt 运行了你的三个示例。
在第一种情况下
void f()
编译为:
S::f():
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
nop
leave
ret
您不必能够阅读所有内容,只需看看当我编译其他两个函数时,它们会生成以下内容:
S::f() &:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
nop
leave
ret
S::f() &&:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov esi, OFFSET FLAT:.LC1
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
nop
leave
ret
我们很清楚,除了加载不同的字符串文字(
.LC0
,.LC1
)之外,它们都是三个相同的。如果我们添加 -O3
,那么所有内容都会内联到 main
中,如下所示:
无裁判资格:
main:
sub rsp, 8
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor eax, eax
add rsp, 8
ret
_GLOBAL__sub_I_main:
sub rsp, 8
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
add rsp, 8
jmp __cxa_atexit
带有参考限定符:
main:
sub rsp, 8
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov esi, OFFSET FLAT:.LC1
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov esi, OFFSET FLAT:.LC1
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor eax, eax
add rsp, 8
ret
_GLOBAL__sub_I_main:
sub rsp, 8
mov edi, OFFSET FLAT:_ZStL8__ioinit
call std::ios_base::Init::Init() [complete object constructor]
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:_ZStL8__ioinit
mov edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
add rsp, 8
jmp __cxa_atexit
这又是相同的(字符串文字除外)
简而言之:无需执行任何操作即可使非 ref 限定函数接受两个参数 - 情况正好相反:编译器阻止 ref 限定函数接受它们技术上可以但不是的参数allowed 采取(既防止程序员做他们不应该做的事情,又防止函数被优化以做一些实际上会在一般情况下中断的事情)。
在某种程度上,它就像类型系统:在机器级别上,计算机只是在位和字节之间移动,并且对于您认为任何给定块代表的类型绝对是零。这纯粹是编译器试图让你承担责任并确保你维护你声明的不变量,但是一旦编译器高兴并发出一些机器代码,那么这些类型就消失了。
或者,因为我喜欢例子,你可以说这就像 VIP 和非 VIP 门之间的区别:门实际上是相同的,但守卫(编译器)只允许你通过(他们认为)的门进入您已获得许可。