gpt4 book ai didi

c++ - C++中的多态

转载 作者:行者123 更新时间:2023-12-01 17:15:50 25 4
gpt4 key购买 nike

据我所知:

C++ 提供了三种不同类型的多态性。

  • 虚函数
  • 函数名重载
  • 运算符重载

  • 除了上述三种多态性外,还存在其他类型的多态性:
  • 运行时
  • 编译时
  • 临时多态性
  • 参数多态性

  • 我知道运行时多态可以通过虚函数来实现
    静态多态可以通过模板函数实现

    但是对于另外两个
  • 临时多态性
  • 参数多态
    website says,

  • 临时多态性:

    如果可以使用的实际类型的范围是有限的,并且在使用之前必须单独指定组合,这称为临时多态性。

    参数多态:

    如果所有代码的编写都没有提及任何特定类型,因此可以透明地与任意数量的新类型一起使用,则称为参数多态性。

    我几乎无法理解他们:(

    如果可能的话,谁能用一个例子来解释它们?
    我希望这些问题的答案对他们大学的许多新毕业生有所帮助。

    最佳答案

    对多态性的理解/要求

    要理解多态性 - 正如计算科学中使用的术语 - 从对它的简单测试和定义开始是有帮助的。考虑:

        Type1 x;
    Type2 y;

    f(x);
    f(y);

    这里, f() 是执行一些操作,并被赋予值 xy 作为输入。

    To exhibit polymorphism, f() must be able to operate with values of at least two distinct types (e.g. int and double), finding and executing distinct type-appropriate code.



    C++ 多态机制

    显式程序员指定的多态性

    您可以编写 f() 以便它可以通过以下任何一种方式对多种类型进行操作:
  • 预处理:
    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
  • 重载:
    void f(int& x)    { x += 2; }

    void f(double& x) { x += 2; }
  • 模板:
    template <typename T>
    void f(T& x) { x += 2; }
  • 虚拟调度:
    struct Base { virtual Base& operator+=(int) = 0; };

    struct X : Base
    {
    X(int n) : n_(n) { }
    X& operator+=(int n) { n_ += n; return *this; }
    int n_;
    };

    struct Y : Base
    {
    Y(double n) : n_(n) { }
    Y& operator+=(int n) { n_ += n; return *this; }
    double n_;
    };

    void f(Base& x) { x += 2; } // run-time polymorphic dispatch

  • 其他相关机制

    编译器为内建类型提供的多态性、标准转换和强制转换/强制转换将在后面讨论,以确保完整性:
  • 无论如何,它们通常都能直观地理解(保证“哦,那个” react ),
  • 它们影响了要求和无缝使用上述机制的阈值,以及
  • 解释是对更重要概念的繁琐的干扰。

  • 术语

    进一步分类

    鉴于上述多态机制,我们可以通过多种方式对它们进行分类:
  • 什么时候选择多态类型特定代码?
  • 运行时 意味着编译器必须为程序在运行时可能处理的所有类型生成代码,并在运行时选择正确的代码(虚拟调度)0x21423419
  • 编译时间 表示在编译期间选择特定类型的代码。这样做的结果是:假设一个程序仅在上面称为 f 并带有 int 参数 - 根据所使用的多态机制和内联选择,编译器可能会避免为 f(double) 生成任何代码,或者在编译或链接时可能会丢弃某些生成的代码。 (上述所有机制,除了虚拟调度)
  • 支持哪些类型?
  • Ad-hoc 意味着您提供明确的代码来支持每种类型(例如重载、模板特化);您明确地添加了“为此”(根据临时的含义)类型、其他一些“这个”,也许还有“那个”的支持;-)。
  • Parametric 意味着您可以尝试将该函数用于各种参数类型,而无需专门做任何事情来启用对它们的支持(例如模板、宏)。一个具有像模板/宏所期望的那样的函数/运算符的对象就是模板/宏完成其工作所需的全部内容,确切的类型是无关紧要的。 C++20 引入的“概念”表达并强制执行这样的期望 - 参见 cppreference page here
  • 参数多态性提供了 鸭子类型 - 这个概念归功于 James Whitcomb Riley,他显然说“当我看到一只像鸭子一样走路和像鸭子一样游泳的鸟时,我会像鸭子一样叫。”
    template <typename Duck>
    void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }

    do_ducky_stuff(Vilified_Cygnet());
  • 子类型(又名包含)多态性 允许您在不更新算法/函数的情况下处理新类型,但它们必须派生自相同的基类(虚拟分派(dispatch))112219319341142

  • 1 - 模板非常灵活。 SFINAE(另请参见 std::enable_if )有效地允许对参数多态性进行几组期望。例如,您可能会编码,当您正在处理的数据类型具有 .size() 成员时,您将使用一个函数,否则另一个不需要 .size() 的函数(但可能会以某种方式受到影响 - 例如使用较慢的 0x2518121413没有在日志中打印有用的消息)。当使用特定参数实例化模板时,您还可以指定临时行为,或者保留一些参数参数( partial template specialisation )或不保留( full specialisation )。

    “多态”

    Alf Steinbach 评论说,在 C++ 标准 中多态 仅指使用虚拟分派(dispatch)的运行时多态性。一般比较科学。根据 C++ 创造者 Bjarne Stroustrup 的词汇表( http://www.stroustrup.com/glossary.html),含义更具包容性:

    polymorphism - providing a single interface to entities of different types. Virtual functions provide dynamic (run-time) polymorphism through an interface provided by a base class. Overloaded functions and templates provide static (compile-time) polymorphism. TC++PL 12.2.6, 13.6.1, D&E 2.9.



    这个答案 - 就像问题一样 - 将 C++ 特性与 Comp.科学。术语。

    讨论

    使用比 Comp 更窄的“多态性”定义的 C++ 标准。科学。社区,以确保您的听众相互理解,请考虑...
  • 使用明确的术语(“我们可以使此代码可重用于其他类型吗?”或“我们可以使用虚拟调度吗?”而不是“我们可以使此代码多态?”),和/或
  • 清楚地定义了您的术语。

  • 尽管如此,成为一名出色的 C++ 程序员的关键是了解多态性真正为您做什么......

    让您编写一次“算法”代码,然后将其应用于多种类型的数据

    ...然后非常了解不同的多态机制如何满足您的实际需求。

    运行时多态适合:
  • 输入由工厂方法处理并作为异构对象集合吐出,通过 strlen() s,
  • 基于配置文件、命令行开关、UI 设置等在运行时选择的实现,
  • 实现在运行时有所不同,例如状态机模式。

  • 当运行时多态性没有明确的驱动程序时,编译时选项通常是可取的。考虑:
  • 模板类的编译-所谓的方面比在运行时失败的胖接口(interface)更可取
  • SFINAE
  • CRTP
  • 优化(许多包括内联和死代码消除、循环展开、基于静态堆栈的数组与堆)
  • Base*__FILE__ 、字符串文字连接和宏的其他独特功能(仍然是邪恶的 ;-))
  • 支持模板和宏测试语义使用,但不要人为限制提供支持的方式(因为虚拟调度往往需要完全匹配的成员函数覆盖)

  • 支持多态性的其他机制

    正如所 promise 的,为了完整性,涵盖了几个外围主题:
  • 编译器提供的重载
  • 转换
  • 强制转换/强制

  • 这个答案最后讨论了上述内容如何结合以增强和简化多态代码 - 特别是参数多态性(模板和宏)。

    映射到特定类型操作的机制

    > 隐式编译器提供的重载

    从概念上讲,编译器为内置类型重载了许多运算符。它在概念上与用户指定的重载没有区别,但被列出是因为它很容易被忽视。例如,您可以使用相同的符号 __LINE__ 添加到 int s 和 double s 并且编译器产生:
  • 特定于类型的 CPU 指令
  • 相同类型的结果。

  • 重载然后无缝扩展到用户定义的类型:
    std::string x;
    int y = 0;

    x += 'c';
    y += 'c';

    编译器提供的基本类型重载在高级 (3GL+) 计算机语言中很常见,对多态性的明确讨论通常意味着更多。 (2GLs - 汇编语言 - 通常要求程序员为不同的类型明确使用不同的助记符。)

    > 标准转换

    C++ 标准的第四部分描述了标准转换。

    第一点总结得很好(来自旧草案 - 希望仍然基本正确):

    -1- Standard conversions are implicit conversions defined for built-in types. Clause conv enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:


  • 来自以下集合的零个或一个转换:左值到右值转换、数组到指针转换和函数到指针转换。
  • 来自以下集合的零个或一个转换:整数提升、浮点提升、整数转换、浮点转换、浮点整数转换、指针转换、成员指针转换和 bool 转换。
  • 零个或一个资格转换。

  • [Note: a standard conversion sequence can be empty, i.e., it can consist of no conversions. ] A standard conversion sequence will be applied to an expression if necessary to convert it to a required destination type.



    这些转换允许以下代码:
    double a(double x) { return x + 2; }

    a(3.14);
    a(42);

    应用较早的测试:

    To be polymorphic, [a()] must be able to operate with values of at least two distinct types (e.g. int and double), finding and executing type-appropriate code.


    x += 2 本身运行专门针对 a() 的代码,因此不是多态的。

    但是,在对 double 的第二次调用中,编译器知道为“浮点提升”(标准第 4 节)生成适合类型的代码,以将 a() 转换为 42 。额外的代码在调用函数中。我们将在结论中讨论这一点的重要性。

    > 强制、强制转换、隐式构造函数

    这些机制允许用户定义的类指定类似于内置类型的标准转换的行为。我们来看一下:
    int a, b;

    if (std::cin >> a >> b)
    f(a, b);

    在这里,对象 42.0 在 bool 上下文中在转换运算符的帮助下进行评估。这可以在概念上与上述主题中的标准转换中的“整体促销”等归为一组。

    隐式构造函数有效地做同样的事情,但由强制转换类型控制:
    f(const std::string& x);
    f("hello"); // invokes `std::string::string(const char*)`

    编译器提供的重载、转换和强制的含义

    考虑:
    void f()
    {
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
    }

    如果我们希望在除法过程中将 std::cin 视为实数(即 6.5 而不是四舍五入为 6),我们只需要更改为 x

    这很好,但是让代码明确地“类型正确”不会有太多工作:
    void f()                               void f()
    { {
    typedef int Amount; typedef double Amount;
    Amount x = 13; Amount x = 13.0;
    x /= 2; x /= 2.0;
    std::cout << double(x) * 1.1; std::cout << x * 1.1;
    } }

    但是,考虑到我们可以将第一个版本转换为 typedef double Amount :
    template <typename Amount>
    void f()
    {
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
    }

    正是由于那些小小的“便利功能”,它可以很容易地为 templateint 实例化,并按预期工作。如果没有这些功能,我们将需要显式转换、类型特征和/或策略类,以及一些冗长、容易出错的困惑,例如:
    template <typename Amount, typename Policy>
    void f()
    {
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
    }

    因此,编译器为内置类型提供运算符重载、标准转换、强制转换/强制/隐式构造函数——它们都为多态性提供了微妙的支持。根据此答案顶部的定义,他们通过映射解决了“查找和执行适合类型的代码”问题:
  • “远离”参数类型
  • 来自多种数据类型的多态算法代码句柄
  • 为(可能更少)数量的(相同或其他)类型编写的代码。
  • 从常量类型的值“到”参数类型

  • 它们本身不会建立多态上下文,但确实有助于授权/简化此类上下文中的代码。

    你可能会觉得被骗了……这似乎并不多。重要的是,在参数多态上下文中(即在模板或宏中),我们试图支持任意大范围的类型,但通常希望根据其他函数、文字和操作来表达对它们的操作,这些操作专为一小部分类型。当操作/值在逻辑上相同时,它减少了在每个类型的基础上创建几乎相同的功能或数据的需要。这些功能相互配合,增加了“尽力而为”的态度,通过使用有限的可用功能和数据来做直觉上预期的事情,并且只有在真正有歧义时才停止错误。

    这有助于限制对支持多态代码的多态代码的需求,围绕多态的使用绘制更紧密的网络,因此本地化使用不会强制广泛使用,并根据需要使多态的好处可用,而无需增加必须在以下位置公开实现的成本编译时,在目标代码中具有相同逻辑函数的多个拷贝以支持所使用的类型,并在执行虚拟调度而不是内联或至少编译时解析调用。正如在 C++ 中的典型情况一样,程序员有很大的自由来控制使用多态的边界。

    关于c++ - C++中的多态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5854581/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com