【问题标题】:Polymorphism in C++ does not work correctly with referenceC++ 中的多态性在引用时无法正常工作
【发布时间】:2026-02-15 07:55:02
【问题描述】:

我有一个简单的代码,它不能与引用(多态性)一起正常工作。

#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 的默认复制ctor,因为您从未提供过。默认值在第二个示例中比在第一个示例中更合适(显式类型为Base)。为了证明这一点,请在Derived(Base&amp;) 中设置断点或输出调试消息。您应该看到它在第二个示例中没有被触发。换句话说,d1 只是 d2 的一个副本。

标签: c++ pointers reference polymorphism


【解决方案1】:

您正在调用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 类型)。因此,这不是最大样本的部分原因。

【讨论】:

    【解决方案2】:

    如果您检测代码,您将在调用 Derived d2(d1) 时看到 Derived::Derived(Base&) 构造函数没有被调用。这是因为 d1 参数更适合 隐式复制构造函数,它只是将 b 成员从 d1 复制到 d2。

    为了查看您期望的行为,您可以将 d1 显式转换为 (Base&amp;)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
    

    【讨论】:

    • 呃。没有 D(B&) 构造函数的另一个原因!
    【解决方案3】:

    您的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);
    

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

    【讨论】:

    • 我相信他知道这一点,他想知道为什么他提出的当前方法行不通。
    【解决方案4】:

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

    【讨论】: