指向派生 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
将不会覆盖它。
如果删除上面的 virtual
和 override
,您可以看到当派生 class 中的方法没有覆盖基 class 中的方法时会发生什么。然后调用 b.say_hello()
将调用 base::say_hello()
,无论 base
或 derived
是否传递给 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&
),而不必担心 UdpTransport
或 TcpTransport
是。它允许一部分代码在不知道另一部分代码在做什么的情况下工作。
许多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'
。它认为:嗯,b
是 Base
类型,所以我要发出对 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!";
}
};
然后我们得到预期的输出。
我是 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
将不会覆盖它。
如果删除上面的 virtual
和 override
,您可以看到当派生 class 中的方法没有覆盖基 class 中的方法时会发生什么。然后调用 b.say_hello()
将调用 base::say_hello()
,无论 base
或 derived
是否传递给 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&
),而不必担心 UdpTransport
或 TcpTransport
是。它允许一部分代码在不知道另一部分代码在做什么的情况下工作。
许多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'
。它认为:嗯,b
是 Base
类型,所以我要发出对 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!";
}
};
然后我们得到预期的输出。