【问题标题】:Why does this virtual method return true?为什么这个虚方法返回true?
【发布时间】:2015-07-17 22:10:05
【问题描述】:

在学习 C++ 中的多态性教程时,我发现一些代码在调用非覆盖的虚拟方法时表现异常。以下是课程:

// classes.cpp

namespace Classes
{
    class C
    {
    public:
        virtual bool has_eyesight()
        {
            return false;
        }
    } c;

    class See : public C
    {
    public:
        bool has_eyesight() override
        {
            return true;
        }
    } si;
}

这里是主要方法:

// file.cpp

#include <iostream>
#include "classes.cpp"

using std::cout;
using std::endl;
using Classes::C;
using Classes::See;

int main()
{
    See& si = Classes::si;

    cout << si.has_eyesight() << endl;

    C& c = si;

    cout << c.has_eyesight() << endl;

    c = Classes::c;

    cout << c.has_eyesight() << endl;
}

此代码在运行时将打印1 1 1 (true true true);如果 c.has_eyesight() 引用的是 C 而不是 See,它不应该返回 false 吗?

(如果这听起来很幼稚,请原谅我,我刚开始学习 C++。)

【问题讨论】:

  • c= 类::c;没有做你认为的事情。
  • @tp1 那它有什么作用呢?你能在答案中解释一下吗?

标签: c++ reference polymorphism virtual


【解决方案1】:

让我们来看看你在这里做什么。

C& c = si;

c 现在是对Classes::si 的引用,它是See 的一个实例。所以它的vtable指向See的vtable,has_eyesight()会返回true。

引用和指针之间的主要区别在于您不能修改它的目标 - 无论您做什么,c 现在总是指向Classes::si。意思...

c = Classes::c;

这不会更改对Classes::c 的引用。您不能修改参考。相反,它调用 c 上的赋值运算符,所以现在您将 Classes::c 复制到 Classes::si 上。除非您重载 operator=,否则这将执行逐个成员的复制。它不会修改vtable,所以has_eyesight()会继续返回true。

如果你确实想让它指向别的东西,你将不得不使用一个指针:

See* si = &Classes::si;
cout << si->has_eyesight() << endl;
C* c = si;
cout << c->has_eyesight() << endl;
c = &Classes::c;
cout << c->has_eyesight() << endl;

试试这个。

【讨论】:

  • 这有点出乎意料。这是否意味着您应该始终在多态类中将 operator= 设为虚拟?
  • @WorldSEnder 不明白为什么你会发现这出乎意料,but you are free to ask
  • 啊,所以我看到了:复制赋值用@9​​87654339@的字段覆盖C的字段对应的See的字段,但它基本上没有触及方法。奇怪的东西;我想知道为什么他们没有抛出某种类型转换/运行时错误而不是让这种情况发生。
  • 显然将一个C 分配给另一个是有效的,因为它们是相同的类型。继承存在“is-a”关系。 See 也是合法的C。这又回到了我的第一句话:将一个C 分配给另一个是有效的。
  • 此外,分配给对象之后可能会处于意外状态:派生类中的字段不会(显然)被分配给,这意味着对象的一部分仍处于分配之前的状态(切片)。这实际上是类型系统中的泄漏示例。
【解决方案2】:

代码

c = Classes::c;

没有重新分配引用。在 c++ 中重新分配引用是不可能的。相反,它会为基础值赋值。

【讨论】:

  • 我对@RSahu 所说的相同:“为基础价值赋值”究竟是什么意思?
【解决方案3】:

第二个:

C& c = si;

c 是多态访问的,所以只有动态类型计数。 c的动态类型为See,因此选择See::has_eyesight

第三个:

    c = Classes::c;

它不是重新绑定引用,而是将Classes::c 复制到c 本身。引用不能被反弹。

【讨论】:

    【解决方案4】:

    线

    c = Classes::c;
    

    请勿将 c 更改为引用 Classes::c。相反,该行相当于:

    Classes::si = Classes::c;
    

    这不会改变si 的虚拟表。因此,您在使用时继续执行Classes::See::has_eyesight()

    cout << c.has_eyesight() << endl;
    

    【讨论】:

    • 如果你试图将基类分配给派生类,不应该出现运行时错误吗?如果这是真的,那么为什么Classes::si 不变成C 并在调用has_eyesight() 时返回false?
    • 将基类对象分配给派生类对象是完全可以的。当你这样做时,只有对象的基类部分受到影响。该对象仍然是派生类的一个实例,因此,虚拟表仍然对应于派生类。
    猜你喜欢
    • 2019-06-29
    • 2018-01-28
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    • 2013-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多