指向派生 class 对象的基 class 指针的用例是什么

What are the use cases for a base class pointer pointing to a derived class object

我是 OOP 的新手,正在尝试学习 C++,我学习了交叉多态性并使用了 virtual 关键字。 我只是不明白为什么我们可能需要这样做。 我已经检查过这个网站是否有类似的问题,但 none 试图回答 为什么?

大多数多态性的介绍都是这样开始的:

 Base* b = new Derived();  // ouch :/

在我看来,这是非常不幸的,因为它造成了一种误解,即多态性意味着通过 new 进行手动内存管理。主题有些相关。虽然我的多态性示例会这样开始:

#include <iostream>

struct base { 
    virtual void say_hello() { std::cout << "hello base\n"; } 
};
struct derived : base { 
    void say_hello() override { std::cout << "hello derived\n";} 
};

void foo(base& b) {
    b.say_hello();
}

int main()
{
    base b;
    derived d;
    foo(b);
    foo(d);   
}

say_hello 必须声明为 virtual 以允许 derived 覆盖该方法。如果 say_hello 不是虚拟的,那么 derived::say_hello 将不会覆盖它。

如果删除上面的 virtualoverride,您可以看到当派生 class 中的方法没有覆盖基 class 中的方法时会发生什么。然后调用 b.say_hello() 将调用 base::say_hello(),无论 basederived 是否传递给 foo,因为只有对于虚拟方法,要调用的方法才会考虑对象的动态类型。


What are the use cases for a base class pointer pointing to a derived class object

同上参考。 foo(base&) 可以获取从 base 派生的任何类型的对象,然后调用它的 say_hello 方法。如果这不可能,您将不得不编写 foo(derived)foo(derived2)foo(derived3) 来调用他们的 say_hello 方法。简而言之,多态性就是对不同的类型一视同仁。 foo 不需要知道其参数的动态类型是什么。它只需要知道它继承自base.

主要目标是:关注点分离。

让我们以我的日常工作为例。我在一个做网络的代码库上工作。绝大多数代码库都依赖于 class,看起来像:

class Transport
{
    public:
    virtual bool SendMessage(int clientId, string message);
};

假设我有一百个文件和几万行代码,使用这个 class。

现在,我在 low-level 团队的同事想要让我们的代码同时使用 UDP 和 TCP 进行通信。他们可以简单地实现:

class UdpTransport:public Transport
{
    public:
    bool SendMessage(int clientId, string message) override { /* their code */};
};

class TcpTransport:public Transport
{
    public:
    bool SendMessage(int clientId, string message) override { /* their code */};
};

这使我可以保持整个代码不变(仅使用 Transport*Transport&),而不必担心 UdpTransportTcpTransport是。它允许一部分代码在不知道另一部分代码在做什么的情况下工作。

许多Design Patterns (GOF book) 依赖虚函数。这个想法是你让你使用一个你只知道它的接口的对象,当你调用一个函数时,所做的是基于实现接口的对象。

其中一种设计模式是 Command,其中您有一个容器,您可以在其中添加命令实现,并且容器的处理程序调用“执行”函数,而不必担心命令实际是什么.命令实现已经包含它需要的数据 运行.

虚函数的一个常见替代方案是 switch case,它需要知道所有作为枚举的实现,以便调用者知道要准确调用什么或 function-table.

的自己的实现

当然,如果你看不到这样的东西如何改进你的程序,那么最好不要试图强加它。

考虑以下 class 体系结构和功能:

#include <iostream>
#include <string>

class Base
{
public:
    std::string hello() const
    {
        return "Hello Base!";
    }
};

class Derived : public Base
{
public:
    std::string hello() const
    {
        return "Hello Derived!";
    }
};

void outputHello(const Base& b)
{
    std::cout << b.hello() << '\n';
}

我们有:

  • 基础 class 与 hello() 方法 returns "Hello Base!";
  • 使用 hello() 方法派生的 class returns "Hello Derived!";
  • 一个 outputHello(const Base&) 函数,它将对 Base 对象的引用作为其参数,并打印出对其 hello() 方法的调用结果。

现在,你认为下面的程序会输出什么?

int main()
{
    Base b;
    outputHello(b);

    Derived d;
    // Allowed as a reference/pointer to Base can
    // reference/point to a Derived object
    outputHello(d);
}

如果您来自 Java 或类似的 object-oriented 语言,您可能会猜到以下内容:

Hello Base!
Hello Derived!

但它输出 Hello Base! 两次。为什么?其实很简单。当编译器到达 outputHello 函数体时,它会看到 std:cout << b.hello() << '\n'。它认为:嗯,bBase 类型,所以我要发出对 Base::hello() 的调用。它确实做到了这一点,无论我们实际作为参数传递给 outputHello.

的对象类型如何

为了解决这个问题得到我们期望的输出,我们需要RTTI (Runtime Type I信息),其名称不言自明。这是通过将 Base::hello() 标记为 virtual:

来实现的
class Base
{
public:
    virtual std::string hello() const
    {
        return "Hello Base!";
    }
};

Derived::hello() 标记为 override 也是一种很好的做法,尽管这不是必需的 — 覆盖函数自动为虚拟的。

class Derived : public Base
{
public:
    std::string hello() const override
    {
        return "Hello Derived!";
    }
};

然后我们得到预期的输出。