专栏名称: Linux爱好者
伯乐在线旗下账号,「Linux爱好者」专注分享 Linux/Unix 相关内容,包括:工具资源、使用技巧、课程书籍等。
目录
相关文章推荐
51好读  ›  专栏  ›  Linux爱好者

虚函数,C++开发者如何有效利用?

Linux爱好者  · 公众号  · linux  · 2023-02-10 11:50

正文

请到「今天看啥」查看全文


本期博客,我们来介绍C++中的虚函数,并给出一些实际操作的建议。
文末有福利活动彩蛋!!

1虚函数的使用规则

C++ 虚函数必须遵循几个关 键规则:

  • 在基类中使用 virtual 关键词来声明函数

  • 虚函数不能为静态函数

  • 为实现运行时多态,应使用指针或引用来访问虚拟函数

  • 对于基类和派生类而言,此类函数的原型应该相同(允许使用协变式返回类型,我们将在下文进行讨论)

  • 如果基类中含有虚函数,则应该使用虚拟析构函数,防止析构函数调用错误


2 用 C++ 运行虚函数的示例

虚函数在 C++ 中的运行情况:

class Pet {public:    virtual ~Pet() {}    virtual void make_sound() const = 0;};
class Dog: public Pet {public: virtual void make_sound() const override { std::cout << "raf raf\n"; }};
class Cat: public Pet {public: virtual void make_sound() const override { std::cout << "mewo\n"; }};
int main() { Cat mitzi; Dog roxy; Pet *pets[] = {&mitzi, &roxy}; for(auto pPet: pets) { pPet->make_sound(); }}
解释一下上述示例。
Pet 这是一个通用基类。但是我们仍然希望存在一个 make_sound 函数,这样,我们就能在不知道 pet 类型的情况下,在 pet 上调用 make_sound。仅在进行编译时,我们才能知道 pet 类型。因此,我们在基类中声明虚函数 make_sound,用 =0 来将其表示为由派生类实现的纯虚函数。
然后,再由 Dog 和 Cat 来真正实现该函数。实现函数期间,我们添加关键词 override,这样,编译器就能确保函数签名与基类中的签名相匹配。
在 main 中,我们可以在 Pet 指针上调用 make_sound,而无需在编译时知道该指针指向哪种 pet。我们会在运行时,根据实际存在的对象,实现所需函数。
我们必须要强调,这是一个非常简单的示例。我们也有其他解决方案应对这一简单示例(例如,为 pet’s sound 持有数据成员,并避免使用虚函数)。但我们想要展示虚函数的实现过程,因此不对其他解决方案进行额外展示。通常情况下,会使用虚函数为派生类中的不同行为建模,而相应行为不能用简单数据成员来建模。

3 协变式返回类型
我们提到过,若要实现虚函数,派生类函数的签名必须与基类中的签名相匹配。唯一允许的区别是在返回类型上,只要派生类的返回类型是基类返回的派生类型即可。让我们看看下面的示例:
class PetFactory {public:    virtual ~PetFactory() {}    virtual Pet* create() const = 0;} 
class DogFactory: public PetFactory {public: virtual Dog* create() const override { return new Dog(); }};
class CatFactory : public PetFactory {public: virtual Cat* create() const override { return new Cat(); }};
int main() { std::vector pets; DogFactory df; CatFactory cf; PetFactory* petFactory[] = {&df, &cf}; for(auto factory: petFactory) { pets.push_back(factory->create()); } for(auto pPet: pets) { pPet->make_sound(); } for(auto pPet: pets) { delete pPet; }}
在上述示例中,PetFactory 创建函数仅能知道它可以返回 Pet*,但使用协变式返回类型,DogFactory 和 CatFactory 则能知道更为具体的内容,这种虚函数的实现方式仍然行之有效。

2月16日,线上研讨会【C++开发效率提升的三大挑战与解决方案】
扫码文末二维码获取会议入口及C++资料包

1 代码更为灵活、更为通用
这是贯穿所有多态程序的主要优点:根据运行时已知的调用对象,通过允许以不同方式执行函数调用,能使程序更为灵活而通用。如此一来,运行时多态便能从真正意义上使您的代码反映现实——特别是各场景中的对象(或人、动物、形状)并不总是以相同方式执行。

2 代码可复用
通过使用虚函数,我们可以将只应实现一次的通用操作和不同子类中可能有所不同的具体细节区分开来。试想以下示例:如果我们希望实现 prism 类层次结构,则需要在各派生类中分别计算基面积,但可以使用派生类实现基面积计算,从而在基类中实现体积函数。实现代码如下:
class Prism {    double height;public:    virtual ~Prism() {}    virtual double baseArea() const = 0;    double volume() const {        return height * baseArea();    }    // ...};
class Cylinder: public Prism { double radius;public: double baseArea() const override { return radius * radius * std::numbers::pi } // ...};
3 契约式设计
术语“契约式设计”指如果代码设置有执行设计的契约,会比只通过文档来执行设计要好得多。虚函数,特别是纯虚函数,因其决定了在派生类中以不同方式重新实现特定操作的设计决策,可将其视为契约式设计工具。

虚函数的局限性







请到「今天看啥」查看全文