关于[over.ass]/2示例中*bptr = dobj2调用D::operator=(const B&)的疑问
解析
*bptr = dobj2的调用逻辑 嘿,这个问题的核心其实是静态重载解析和动态多态绑定的结合,咱们一步步拆解清楚:
首先先把示例代码贴出来方便对照:
struct B { virtual int operator= (int); virtual B& operator= (const B&); }; struct D : B { virtual int operator= (int); virtual D& operator= (const B&); }; D dobj1; D dobj2; B* bptr = &dobj1; void f() { bptr->operator=(99); // 调用D::operator=(int) bptr->operator=(dobj2); // 调用D::operator=(const B&) dobj1 = dobj2; // 调用隐式声明的D::operator=(const D&) *bptr = dobj2; // 你的疑问点:为什么调用D::operator=(const B&)? }
第一步:明确*bptr的静态类型
bptr是B*类型的指针,解引用后得到的*bptr,它的静态类型是B(编译期能确定的类型),虽然它的动态类型是D(运行期实际指向的对象类型),但重载解析是在编译期完成的,所以首先看静态类型的作用域里的函数。
第二步:*bptr = dobj2的重载解析过程
赋值表达式x = y本质上是调用x.operator=(y)(对于类类型)。这里x是*bptr(静态类型B),所以编译器会在B的作用域里找匹配的operator=函数:
B里有两个重载:int operator=(int)和B& operator=(const B&)。dobj2是D类型,能隐式转换为const B&,所以完美匹配B& operator=(const B&)这个函数。
第三步:动态绑定触发多态调用
因为B& operator=(const B&)是虚函数,而D中重写了这个函数(D& operator=(const B&)是合法的重写,返回类型协变符合规则),所以运行期会根据对象的动态类型(D),调用D中重写的版本,也就是D::operator=(const B&)。
和另外两个场景的对比(帮你强化理解)
- 对于
bptr->operator=(dobj2):你已经理解了,这是直接显式调用成员函数,编译期找到B的virtual B& operator=(const B&),然后动态绑定到D的版本,和*bptr = dobj2的核心逻辑一致,只是写法不同。 - 对于
dobj1 = dobj2:dobj1的静态类型是D,编译器会优先找D自己的operator=。因为你没显式声明D& operator=(const D&),编译器会隐式生成一个拷贝赋值运算符,这个隐式函数的参数是const D&,比继承来的B& operator=(const B&)匹配度更高(参数类型更精确),所以调用的是这个隐式生成的版本。
总结一下:*bptr的静态类型是B,导致编译期匹配到B的虚赋值运算符,再通过动态绑定调用D的重写版本;而dobj1是D类型,编译期匹配到更精确的隐式生成的D拷贝赋值运算符。
内容的提问来源于stack exchange,提问作者Ayrosa
相关产品推荐
相关产品推荐

