C ++中是否存在真正的静态多态性?

时间:2013-12-26 08:20:04

标签: c++ templates runtime override static-polymorphism

以下是C ++中的一个简单代码:

#include <iostream>
#include <typeinfo>

template<typename T>
void function()
{
   std::cout << typeid(T).name() << std::endl;
}

int main()
{
   function<int>();
   function<double>();
   return 0;
}

我已经读过C ++中的模板是编译时功能,它与C#/ Java中的泛型不同。

据我了解,C ++编译器会将一个定义的函数划分为不同的数字(取决于不同类型的调用计数)。

我是对还是不对?我不是C ++编译器的专家,所以我要求你提出一些建议。

如果我对编译器输出的建议是正确的,我想知道我是否可以将上面的代码描述为静态多态

因为它似乎没有覆盖,只是从可执行文件中调用副本或者......应用程序在输出二进制映像中的含义并不重要,但只有重要的部分是在C ++代码级别而且我是'看看编译器如何产生输出。

5 个答案:

答案 0 :(得分:13)

  

C ++中是否存在真正的静态多态?

绝对 - 静态多态有三种机制:模板,宏和函数重载。

  

据我所知,C ++编译器会将单个定义的函数划分为各种函数(取决于具有不同类型的调用计数)。我是对还是不对?

这是一般的想法。实例化的函数数量取决于模板参数的排列数量,可以在function<int>function<double>中明确指定,或者 - 对于使用模板参数匹配函数参数的模板 - 自动从函数参数派生,例如:

template <typename T, size_t N>
void f(T (&array)[N])
{ }

double d[2];
f(d);   // instantiates/uses f<double, 2>()

您应该在可执行二进制映像中最终得到每个实例化模板的单个副本。


  

我想知道我是否可以将上面的代码描述为静态多态?

不是。

  • template<> function实例化为两种类型

    • 至关重要的是,多态性 用于选择function的两个实例中的哪一个在呼叫站点发送

    • 在此类实例化过程中typeid(T)评估intdouble从程序员的角度有效地表现多样性(&#39; sa编译器关键字 - 实现未知)

  • 简单地说,静态和名义上动态的混合(但这里可能是可以优化的静态)多态性支持你使用std::cout

背景 - 多态性和代码生成

我认为对多态至关重要的要求是:

  • 编译代码时(无论是&#34;正常&#34;代码还是每个模板实例化或宏替换),编译器会自动选择(必要时创建) - 并且内联或调用 - 不同类型的行为(机器代码)

    • 即。代码选择/创建由编译器仅基于所涉及的变量的类型来完成,而不是由程序员在不同的函数名称/实例之间进行明确的硬编码,每个函数名称/实例都只能处理

    • 的一种类型或排列类型 例如,
    • std::cout << x;多态调用不同的代码,因为x的类型不同但仍然输出x的值,而非多态{ {1}}处理printf("%d", x),但如果int变为printf("%c", x);,则需要手动修改为x

但是,我们尝试用多态实现的更为宽泛:

  • 在不嵌入显式类型检测和分支代码的情况下,为多种类型的数据重用算法代码

    • 即没有程序源代码包含char - 样式代码
  • 减少了维护负担 ,因为在明确更改变量类型之后,需要在整个源代码中手动进行更少的后续更改

C ++支持以下更大图片方面:

    实例化 相同的源代码 ,以生成 不同的行为 (机器代码),用于某些 其他类型 或类型的排列(这是 的方面 参数 多态性),

    • 实际上称为&#34;实例化&#34;用于模板和&#34;替换&#34;对于预处理器宏,但我会使用&#34; instantiation&#34;以下为方便起见;从概念上讲,重新编译或重新解释......
  1. 隐式调度 (静态或动态) 不同行为 (机器代码)适合于正在处理的数据的 不同类型

  2. ...并在Polymorphism in c++

    的答案中以一些小的方式

    不同类型的多态性涉及其中一种或两种:

    • 发送(2)模板和预处理器时,实例化(1) ,

    • 实例化(1)通常在发送期间发生(2)模板(没有匹配的完全专业化)和功能 - 喜欢(一种循环,虽然宏不会递归扩展)

    • 调度(2)当编译器选择时, 没有 实例化(1)预先存在的函数重载模板专门化,或者当编译器触发虚拟 /动态调度时。

    您的代码实际使用了什么?

    if (type == X) f1(x) else f2(x);function<int>重复使用function<double>模板代码为每种类型创建不同的代码,因此 获取实例化 em>(1)如上所述。但是,您正在硬编码调用哪个实例化而不是让编译器根据某个参数的类型隐式选择实例化,即因此您不要直接使用隐式调度ala(2) )在致电function时。实际上,function缺少编译器可用于隐式选择模板实例化的参数。

    单独的实例化(1) 足以让您的代码考虑使用多态性。不过, 您已经实现了方便的代码重用

    那么什么是明确的多态?

    为了说明模板如何支持dispatch(2)以及实例化(1)和无可争议提供&#34; polymorphism&#34;,请考虑:

    function

    上面的代码还利用隐式调度来键入适当的代码 - aspect&#34; 2。&#34;以上 - 多态性。


    非类型参数

    有趣的是,C ++提供了使用整数参数(例如布尔值,template<typename T> void function(T t) { std::cout << typeid(T).name() << std::endl; } function(4); // note: int argument, use function<int>(...) function(12.3); // note: double argument, use function<double>(...) 和指针常量)实例化模板的功能,并且可以在不改变数据类型的情况下将它们用于各种方式,因此不涉及任何多态性。宏更灵活。


    请注意,在C.R.T.P中使用模板style是 NOT 对静态多态性的要求 - 它是一个示例应用程序。在实例化期间,编译器在将操作与参数指定类型中的实现匹配时展现静态多态性。


    关于术语的讨论

    获得多态性的明确定义很困难。维基百科引用Bjarne Stroustrup的在线词汇表&#34;为不同类型的实体提供单一界面&#34;:这意味着int已经表现出多态性,但恕我直言,我们只有当我们使用时才会获得多态性来自客户端代码的接口的对应关系,例如对于每个实例化,struct X { void f(); }; struct Y { void f(); };都需要对template <typename T> void poly(T& t) { t.f(); }进行静态多态调度。

答案 1 :(得分:5)

维基百科列出了三种类型的多态性:

  
      
  • 如果函数表示不同且可能异构的实现,则取决于单独指定的有限范围   类型和组合,称为 ad hoc多态。特设   使用函数支持多种语言的多态性   重载。

  •   
  • 如果编写的代码没有提及任何特定类型,因此可以透明地使用任意数量的新类型,它是   称为参数多态。在面向对象的编程中   社区,这通常被称为泛型或泛型编程。在   功能编程社区,通常简单地称之为   多态性。

  •   
  • 子类型(或包含多态性)是一个概念,其中名称可以表示许多不同类的实例,只要它们是   由一些常见的超类相关。在面向对象的编程中,   这通常简称为多态。

  •   

第一个是指函数重载。第三种类型是指后期绑定或运行时多态,您可以在继承中看到这种类型。第二个是我们感兴趣的。

模板是编译时构造,类型推导是编译器自动计算出模板参数的过程。这就是静态多态性的来源。

例如:

template <typename T, typename U>
auto func(const T& t, const U& u) -> decltype(t + u)
{
   return (t + u);
}

这适用于兼容plus运算符的任何两种类型。如果编译器可以解决问题,则无需指定模板参数。如果编写执行不同行为的函数重载,例如字符串连接与整数加法,它将是特殊的多态性。

但是,在您的示例中,您拥有不同的函数function<int>function<double>的实例化。这是一个引用:

  

要具有多态性,[a()]必须能够以at值运行   至少两种不同的类型(例如int和double),查找和执行   类型适当的代码。

在这种情况下,实例化特定于它们被实例化的类型,因此不涉及多态性。

答案 2 :(得分:4)

您的示例中没有静态多态性,因为没有多态性。这是因为function<int>()function<double>()看起来不一样。

静态多态的示例包括简单的函数重载,可以使用类型推导的函数模板,类型特征和curiously recurring template pattern (CRTP)。因此,您的示例中的这种变体将符合静态多态性:

#include <iostream>
#include <typeinfo>

template<typename T>
void function(T)
{
   std::cout << typeid(T).name() << std::endl;
}

int main()
{
   function(0);   // T is int
   function(0.0); // T is double
   return 0;
}

这是另一个例子:

template<typename T>
void function(T t)
{
  t.foo();
}

struct Foo() 
{
  void foo() const {}
};

struct Bar() 
{
  void foo() const {}
};

int main()
{
  Foo f;
  Bar b;
  function(f); // T is Foo
  function(b); // T is Bar
}

答案 3 :(得分:2)

对于c ++,术语“静态多态性”通常用于例如CRTP类型设计模式:

template<typename Derived> 
class Base
{
      void someFunc() {
          static_cast<Derived*>(this)->someOtherFunc();
      };
};

class ADerived : public Base<ADerived>
{
    void someOtherFunc() { 
        // ... 
    }
};

它通常意味着在编译/链接时推断和验证类型和继承约束。如果操作丢失或指定类型无效,编译器将发出错误消息。从这个意义上讲,它并不是真正的多态性。

答案 4 :(得分:0)

虽然可以认为OP中的示例没有表现出静态多态性,但使用专门化可以提出更具说服力的案例:

template<class T>
class Base
{
public:
      int a() { return 7; }
};

template<>
class Base<int>
{
public:
      int a() { return 42; }
};

template<>
class Base<double>
{
public:
      int a() { return 121; }
};

我们在这里看到,对于大多数类,a()将返回7; intdouble的专用(派生)实例可以具有完全不同的行为,在简单的情况下通过不同的返回值进行演示,对于具有例如int参数的模板可以进行同样的操作,并且可以展示了可以奇怪地称为静态递归多态性的东西。

虽然术语polymorphic可能正在被拉伸,但这个概念肯定存在。缺少的不是重新定义函数的能力,而是专业类自动继承不改变行为的函数的能力。