C++中的多态性不能与引用一起正常工作

Polymorphism in C++ does not work correctly with reference

本文关键字:常工作 工作 一起 引用 多态性 不能 C++      更新时间:2023-10-16

我有一个简单的代码,它不能正确使用引用(多态性)。

#include <iostream>
#include <string>
class Base {
public:
    Base() {}
    virtual ~Base() {}
    virtual std::string text() const {
        return "Base";
    }
};
class Derived: public Base {
public:
    Derived(Base& _b): b(_b) {}
    virtual ~Derived() {}
    virtual std::string text() const {
        return b.text() + " - Derived";
    }
private:
    Base& b;
};
int main(int argc, char const *argv[])
{
    Base b;
    Derived d1(b);
    std::cout << d1.text() << std::endl;
    Derived d2(d1);
    std::cout << d2.text() << std::endl;
    return 0;
}

输出:

Base - Derived
Base - Derived

我期望输出中的第二行:Base - Derived - Derived。我阅读了一些资源,多态性与引用和指针完美地结合在一起,但在这种情况下,它不是。如果我用指针替换引用,它会再次工作。有人能给我解释一下吗?

非常感谢!

您正在调用Derived的默认复制构造函数。因此,当完成时,d2将是d1的简单成员副本,并且它们的两个b成员将引用相同的Base实例。

要证明这一点,请将其添加到您的Derived类中

class Derived: public Base {
public:
    Derived(Derived& d) : b(d) {}
    Derived(Base& _b): b(_b) {}
    virtual ~Derived() {}
    virtual std::string text() const {
        return b.text() + " - Derived";
    }
private:
    Base& b;
};

有了这个,你的输出将变成:

Base - Derived
Base - Derived - Derived

请注意,这并不是一个伟大的想法,也不是多态性的一个出色的学习例子。(但这是一个有趣的构建覆盖的例子)。还要注意,这不是默认副本构造的典型覆盖(其中参数是const-ref类型)。因此,这不是最大样本的部分原因。

如果您对代码进行指令,您将在调用Derived d2(d1)时看到Derived::Derived(Base&)构造函数未被调用。这是因为d1参数与隐式复制构造函数,它只是将b成员从d1复制到d2。

为了查看预期的行为,可以显式地将d1强制转换为(Base&)d1。如果你这样做因此,您将获得如下代码(使用仪器):

#include <iostream>
#include <string>
class Base {
public:
    Base() {}
    virtual ~Base() {}
    virtual std::string text() const {
        return "Base";
    }
};
class Derived: public Base {
public:
    Derived(Base& _b): b(_b) {std::cout << "init'ed with: " << _b.text() << std::endl;}
    virtual ~Derived() {}
    virtual std::string text() const {
        return b.text() + " - Derived";
    }
private:
    Base& b;
};
int main(int argc, char const *argv[])
{
    std::cout << "Creating Base" << std::endl;
    Base b;
    std::cout << "Creating d1" << std::endl;
    Derived d1(b);
    std::cout << d1.text() << std::endl;
    std::cout << "Creating d2" << std::endl;
    Derived d2(d1);
    std::cout << d2.text() << std::endl;
    std::cout << "Creating d3" << std::endl;
    Derived d3((Base&)d1);
    std::cout << d3.text() << std::endl;
    return 0;
}

这给出了预期的输出:

Creating Base
Creating d1
init'ed with: Base
Base - Derived
Creating d2
Base - Derived
Creating d3
init'ed with: Base - Derived
Base - Derived - Derived

您的d1d2都具有类型Derived,因此可以正常工作。通常,引用是颠倒的;例如

Base b;
Derived d;
Base &dr = d;
std::cout << b.text() << std::endl;
std::cout << dr.text() << std::endl;

这里text()是通过Base类型调用的,但后者将调用Derived中的版本。

请注意,允许通过基类初始化派生类通常是没有意义的。假设您添加的类型Derived2的能力或状态与Derived大不相同。此构造函数将允许

Derived2 d2;
Derived d1(d2);

这可能是一个非常糟糕的主意。

正如注释中正确指出的那样,它现在使用默认的复制构造函数,这就是您观察到两者输出相同的原因。因此,d1只是复制到d2中,而不是用于d2中的基成员变量。