网站集约化建设意见,公司推广渠道,wordpress 建视频网站吗,WordPress怎么支持https1 什么是虚函数#xff1f;1.1 虚函数的使用规则1.2 用 C 运行虚函数的示例1.3 协变式返回类型2 在 C 中使用虚函数的优点2.1 代码更为灵活、更为通用2.2 代码可复用2.3 契约式设计3 虚函数的局限性3.1 性能3.2 设计问题3.3 调试#xff0c;容易出错4 虚函数的替代方案4.1 仅…1 什么是虚函数1.1 虚函数的使用规则1.2 用 C 运行虚函数的示例1.3 协变式返回类型2 在 C 中使用虚函数的优点2.1 代码更为灵活、更为通用2.2 代码可复用2.3 契约式设计3 虚函数的局限性3.1 性能3.2 设计问题3.3 调试容易出错4 虚函数的替代方案4.1 仅使用数据成员4.2 变体4.3 函数式编程4.4 静态多态5 用 Incredibuild 加速您的 C 虚函数6 总 结6.1 什么是 C 的虚函数6.2 虚函数存在哪些问题6.3 在 C 中虚函数有何替代方案1 什么是虚函数
虚函数是基类中声明的成员函数且使用者期望在派生类中将其重新定义。那么在 C 中什么是虚函数呢在 C 中通常将虚函数用于实现运行时多态该特性由 C 提供适用于面向对象编程。我们将在下文更为详细地讨论运行时多态。不论函数调用所使用的指针或引用类型如何虚函数最为重要的工作是确保函数调用正确。
1.1 虚函数的使用规则
C 虚函数必须遵循几个关键规则
[✔] 在基类中使用 virtual 关键词来声明函数[✔] 虚函数不能为静态函数[✔] 为实现运行时多态应使用指针或引用来访问虚拟函数[✔] 对于基类和派生类而言此类函数的原型应该相同允许使用协变式返回类型我们将在下文进行讨论[✔] 如果基类中含有虚函数则应该使用虚拟析构函数防止析构函数调用错误
1.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 持有数据成员并避免使用虚函数。但我们想要展示虚函数的实现过程因此不对其他解决方案进行额外展示。通常情况下会使用虚函数为派生类中的不同行为建模而相应行为不能用简单数据成员来建模。
1.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::vectorPet* 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 在 C 中使用虚函数的优点
现在如果您已经花费时间研究过 C可能会注意到不需要由虚函数来重新定义派生类中的基函数。但存在这样的巨大区别使得虚函数不可或缺虚函数覆写基类函数从而实现运行时多态。从本质上讲多态指一个函数或对象以不同方式执行的能力具体情况视使用方式而定。这属于面向对象编程的关键特性——结合其他众多特性使得 C 作为编程语言而有别于 C 语言。
2.1 代码更为灵活、更为通用
这是贯穿所有多态程序的主要优点根据运行时已知的调用对象通过允许以不同方式执行函数调用能使程序更为灵活而通用。如此一来运行时多态便能从真正意义上使您的代码反映现实——特别是各场景中的对象或人、动物、形状并不总是以相同方式执行。
2.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}// ...
};2.3 契约式设计
术语“契约式设计”指如果代码设置有执行设计的契约会比只通过文档来执行设计要好得多。虚函数特别是纯虚函数因其决定了在派生类中以不同方式重新实现特定操作的设计决策可将其视为契约式设计工具。
3 虚函数的局限性
虚函数功能极为强大但它们并非毫无缺点。开始使用虚函数前您应该注意以下事项
3.1 性能
无论是在运行时性能还是在内存方面虚函数成本都要比普通函数高。
内存部分通常冗余取决于实现方式但最为常见的是每个对象都有一个额外内部指针。这并不是什么大问题除非我们有数以百万计的小对象这些小对象的额外指针可能会引起内存问题。
函数的运行时性能成本不是一次跳转而是两次跳转或者如果可以内联函数性能成本就是两次跳转而不是零次跳转。虚函数需要跳转到虚函数表再跳转到函数本身。这种额外跳转增加了 CPU 指令缓存中指令未准备就绪的概率因此这两次跳转并非唯一成本。
最后如果您需要实现多态与其他替代方案相比性能方面的额外成本通常也在情理之中。然而若要将第一个虚函数添加到类中通常需要考虑额外成本。
3.2 设计问题
继承特别是虚函数会引起设计问题。继承层次结构设计糟糕可能会导致类膨胀和类之间关系异常。
从构造函数和析构函数调用虚函数的规则也会影响您的设计。从构造函数和析构函数调用的任何虚函数都不是多态函数这样一来有时需要将操作从构造函数转移到 init 虚拟函数。
为避免糟糕设计应切记继承和多态并非是应对任何问题的最佳解决方案。
3.3 调试容易出错
讽刺的是虚函数面临的挑战之一是缺乏弹性。
由于需要遵循调用流程调试虚函数调用可能会变得稍显混乱。一般来说遵循函数调用并不十分困难但根据对象类型在遵循隐藏调度方面仍然需要进行额外工作。调试器会自行纠正错误但决定断点位置可能会变得更加困难。
至于更容易出错在某些情况下不应调用虚函数的基类实现而在某些情况下应在开始时调用有时也在结束时调用。由于忘记调用基类实现或是在错误的地方、不需要的时候调用使用虚函数极其容易出错。
可将其视为契约式设计工具。
4 虚函数的替代方案
4.1 仅使用数据成员
第一种替代方案是尝试并对基于简单数据成员的不同行为进行建模。如果不同类型的唯一区别是 sound那就将其转换为数据成员在构造时进行初始化这样就没有问题了。但在许多情况下行为更加复杂需要不同的实现方式。
4.2 变体
另一种方案是使用 std::variant 和 std::visit特别是待支持的不同类型已知且列表不会太长时二者可能相关。您可以点击此处和此处关于该方案的信息。
4.3 函数式编程
您可以传递待执行的操作将其作为函数对象的 lambda或者作为旧有 C 样式的函数指针随后对其进行建模而无需在类层次结构中对不同操作进行建模。通过该方法您能将数据模型和可能想要执行的操作区分开来这带来了极高灵活性。
4.4 静态多态
静态多态是一种基于模板的方法用于获取多态动态但基于编译时已知的实际想要使用的类型。例如您可能希望代码同时支持 UDPConnection 和 TCPConnection但在编译时您可能想要知道使用 UDPConnection 或 TCPConnection 的具体流程。基于模板的静态多态可以实现更佳性能。 一些替代技术可能会导致项目编译时间变长。我们认为特别是当您使用 Incredibuild 来加速构建时这不会影响您的决策设计。首先选择合适的设计方案然后使用正确工具来缩减编译时间即可。
5 用 Incredibuild 加速您的 C 虚函数
如果您想在不严重拖累编译速度和构建进程的情况下从虚拟函数中受益您就需要强大的计算能力作为后援。 Incredibuild 能够做到这一点。通过在虚拟机在本地网络上分配编译任务Incredibuild 从根本上加快了 C 的编译速度。此外Incredibuild 能与时下主流编译器和构建系统无缝集成包括 Visual Studio、Qt Creator 和 Clang。 如此一来虚函数便能具备极高的灵活性和效率而无需花费时间来等待代码编译。首先选择合适的设计方案然后使用正确工具来缩减编译时间即可。
6 总 结
6.1 什么是 C 的虚函数
虚拟函数是基类中声明的成员函数将在派生类中重新定义。在 C 中使用虚函数来实现运行时多态。
6.2 虚函数存在哪些问题
在运行时性能和内存使用方面相比于普通函数虚拟函数会造成更多影响。此外虚拟函数会产生基于继承层次结构的设计问题导致类膨胀和关系异常。最后虚拟函数由于存在函数调用问题往往难以进行调试。由于调用顺序的不可预测性使用虚拟函数更容易引发错误。
6.3 在 C 中虚函数有何替代方案
是的为了实现更好的设计或者是获得更佳的性能您可能要考虑一些替代方案。但鉴于 C 程序员普遍使用虚函数您应将其视为工具包内的一项工具必要时加以使用。 如果您选择了另一种替代方案比如基于模板的静态多态切勿让较长的编译时间影响您的设计方案。确保选用合适的工具来加速构建进程如果您没有使用 Incredibuild请了解我们的解决方案看看 Incredibuild 在减少编译时间方面可实现的惊人效果。