为啥虚拟分配的行为与相同签名的其他虚拟功能不同?
Posted
技术标签:
【中文标题】为啥虚拟分配的行为与相同签名的其他虚拟功能不同?【英文标题】:Why does virtual assignment behave differently than other virtual functions of the same signature?为什么虚拟分配的行为与相同签名的其他虚拟功能不同? 【发布时间】:2010-11-01 10:31:31 【问题描述】:在玩实现虚拟赋值运算符时,我以一个有趣的行为结束。这不是编译器故障,因为 g++ 4.1、4.3 和 VS 2005 具有相同的行为。
基本上,就实际执行的代码而言,virtual operator= 的行为与任何其他虚函数不同。
struct Base
virtual Base& f( Base const & )
std::cout << "Base::f(Base const &)" << std::endl;
return *this;
virtual Base& operator=( Base const & )
std::cout << "Base::operator=(Base const &)" << std::endl;
return *this;
;
struct Derived : public Base
virtual Base& f( Base const & )
std::cout << "Derived::f(Base const &)" << std::endl;
return *this;
virtual Base& operator=( Base const & )
std::cout << "Derived::operator=( Base const & )" << std::endl;
return *this;
;
int main()
Derived a, b;
a.f( b ); // [0] outputs: Derived::f(Base const &) (expected result)
a = b; // [1] outputs: Base::operator=(Base const &)
Base & ba = a;
Base & bb = b;
ba = bb; // [2] outputs: Derived::operator=(Base const &)
Derived & da = a;
Derived & db = b;
da = db; // [3] outputs: Base::operator=(Base const &)
ba = da; // [4] outputs: Derived::operator=(Base const &)
da = ba; // [5] outputs: Derived::operator=(Base const &)
效果是,通过在通过真实派生对象调用时调用运算符的基本版本( [1]) 或派生引用 ([3]),而当通过基引用 ([2]) 调用时,或者当左值或右值是基引用而另一个是派生引用 ([ 4],[5])。
对这种奇怪的行为有什么合理的解释吗?
【问题讨论】:
【参考方案1】:事情是这样的:
如果我将 [1] 更改为
a = *((Base*)&b);
然后事情就会按照您的预期进行。 Derived
中有一个自动生成的赋值运算符,如下所示:
Derived& operator=(Derived const & that)
Base::operator=(that);
// rewrite all Derived members by using their assignment operator, for example
foo = that.foo;
bar = that.bar;
return *this;
在您的示例中,编译器有足够的信息来猜测 a
和 b
的类型为 Derived
,因此他们选择使用上面调用您的自动生成的运算符。这就是你得到[1]的方式。我的指针转换强制编译器按照你的方式去做,因为我告诉编译器“忘记”b
的类型是Derived
,所以它使用Base
。
其他结果同理。
【讨论】:
这里不涉及猜测。规则非常严格。 谢谢,真正的答案(已由三个人发布)是编译器为派生类生成的 operator= 隐式调用 Base::operator=。我将其标记为“已接受的答案”,因为它是第一个。a = static_cast<Base &>(b);
将是一种避免 C 风格转换的方法(这会带来意外进行重新解释转换的风险)【参考方案2】:
在这种情况下有三个 operator=:
Base::operator=(Base const&) // virtual
Derived::operator=(Base const&) // virtual
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly
这解释了为什么在案例 [1] 中看起来 Base::operator=(Base const&) 被称为“虚拟”。它是从编译器生成的版本中调用的。这同样适用于案例[3]。在情况 2 中,右侧参数 'bb' 的类型为 Base&,因此无法调用 Derived::operator=(Derived&)。
【讨论】:
【参考方案3】:没有为派生类定义用户提供的赋值运算符。因此,编译器合成一个,并且内部基类赋值运算符是从派生类的合成赋值运算符中调用的。
virtual Base& operator=( Base const & ) //is not assignment operator for Derived
因此,a = b; // [1] outputs: Base::operator=(Base const &)
在派生类中,基类赋值运算符已被覆盖,因此,被覆盖的方法在派生类的虚拟表中获取一个条目。当通过引用或指针调用方法时,由于在运行时解析 VTable 条目,将调用派生类覆盖的方法。
ba = bb; // [2] outputs: Derived::operator=(Base const &)
==>内部 ==> (Object->VTable[赋值运算符]) 获取对象所属类的VTable中赋值运算符的入口并调用方法。
【讨论】:
【参考方案4】:如果您未能提供适当的operator=
(即正确的返回和参数类型),则默认的operator=
由编译器提供,它会重载任何用户定义的operator=
。在您的情况下,它会在复制派生成员之前调用Base::operator= (Base const& )
。
查看link 了解有关 operator= 被虚拟化的详细信息。
【讨论】:
【参考方案5】:原因是编译器提供了默认赋值operator=
。
在场景 a = b
中调用它,并且我们知道默认内部调用基本赋值运算符。
更多关于虚拟赋值的解释可以在:https://***.com/a/26906275/3235055
【讨论】:
以上是关于为啥虚拟分配的行为与相同签名的其他虚拟功能不同?的主要内容,如果未能解决你的问题,请参考以下文章
centos7的FTP服务vsftpd里建立虚拟用户不同目录分配不同权限