聊聊 C# 和 C++ 中的 泛型模板 底层玩法

最近在看 C++ 的方法和类模板,我就在想 C# 中也是有这个概念的,不过叫法不一样,人家叫 模板,我们叫 泛型,哈哈,有点意思,这一篇我们来聊聊它们底层是怎么玩的?

一:C++ 中的模板玩法

毕竟 C++ 是兼容 C 语言,而 C 是过程式的玩法,所以 C++ 就出现了两种模板类型,分别为: 函数模板类模板,下面简单分析一下。

1. 函数模板的玩法

玩之前先看看格式: template <typename t> rettype funcname (parameter list) { }</typename>

说实话,我感觉 C++ 这一点就做的非常好,人家在开头就特别强调了,这是一个 template,大家不要搞错了,按照这个格式,我们来一个简单的 Sum 操作,参考代码如下:


#include

//求和函数
template  T getsum(T  t1, T  t2) {
    return t1 + t2;
}

int main() {

    int sum1 = getsum(10, 10);

    long sum2 = getsum(20, 20);

    printf("output: int:sum=%d, long: sum=%ld", sum1, sum2);
}

聊聊 C# 和 C++ 中的 泛型模板 底层玩法

接下来我就很好奇,这种玩法和 &#x666E;&#x901A;&#x65B9;&#x6CD5; 调用有什么不同,要想找到答案,可以用 IDA 去看它的静态汇编代码。

聊聊 C# 和 C++ 中的 泛型模板 底层玩法

从静态反汇编代码看,当前生成了两个函数符号分别为: j_??$getsum@H@@YAHHH@Zj_??$getsum@J@@YAJJJ@Z,现在我们就搞清楚了,原来一旦给 &#x6A21;&#x677F; 指定了具体类型,它就生成了一个新的函数符号。

乍一看这句话好像没什么问题,但如果你心比较细的话,会发现一个问题,如果我调用两次 getsum<int></int> 方法,那会生成两个具体函数吗? 为了寻找答案,我们修改下代码:


int main() {

    int sum1 = getsum(10, 10);

    int sum2 = getsum(15, 15);
}

然后再用 IDA 查看一下。

聊聊 C# 和 C++ 中的 泛型模板 底层玩法

哈哈,可以发现这时候并没有生成一个新的 &#x51FD;&#x6570;&#x7B26;&#x53F7;,其实往细处说: j_??$getsum@H@@YAHHH@Z&#x51FD;&#x6570;&#x7B7E;&#x540D;组合出来的名字,因为它们签名一致,所以在编译阶段必然就一个了。

2. 类模板的玩法

首先看下类模板的格式: template <typename t1, typename t2, ...> class className { };</typename>

还是那句话,开头一个 template 暴击,告诉你这是一个模板 😄😄😄, 接下来上一段代码:


#include

template  class Calculator
{
public:
    T getsum(T a1, T b1) {
        return a1 + b1;
    }
};

int main() {

    Calculator cal1;
    int sum1 = cal1.getsum(10, 10);

    Calculator cal2;
    int sum2 = cal2.getsum(15, 15);

    printf("output: sum1=%d, sum2=%ld", sum1,sum2);
}

接下来直接看 IDA 生成的汇编代码。

聊聊 C# 和 C++ 中的 泛型模板 底层玩法

从上面的方法签名组织上看,有点意思, &#x7C7B;&#x540D;+&#x65B9;&#x6CD5;&#x540D; 柔和到一个函数符号上去了,可以看到符号不一样,说明也是根据模板实例化出的两个方法。

二:C# 中的模板玩法

接下来我们看下 C# 中如何实现 getsum 方法,当我把代码 copy 到 C# 中,我发现不能实现简单的 &#x6CDB;&#x578B;&#x53C2;&#x6570; 加减乘除操作,这就太搞了,网上找了下实现方式,当然也可以让 T 约束于 unmanaged,那就变成指针玩法了。


namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Calculator calculator1 = new Calculator();
            Calculator calculator2 = new Calculator();

            int sum1 = calculator1.getsum(10, 10);

            long sum2 = calculator2.getsum(15, 15);

            Console.WriteLine($"sum={sum1}, sum2={sum2}");
            Console.ReadLine();
        }
    }

    public class Calculator where T : struct, IComparable
    {
        public T getsum(T a1, T b1)
        {
            if (typeof(T) == typeof(int))
            {
                int a = (int)Convert.ChangeType(a1, typeof(int));
                int b = (int)Convert.ChangeType(b1, typeof(int));

                int c = a + b;
                return (T)Convert.ChangeType(c, typeof(T));
            }
            else if (typeof(T) == typeof(float))
            {
                float a = (float)Convert.ChangeType(a1, typeof(float));
                float b = (float)Convert.ChangeType(b1, typeof(float));

                float c = a + b;
                return (T)Convert.ChangeType(c, typeof(T));
            }
            else if (typeof(T) == typeof(double))
            {
                double a = (double)Convert.ChangeType(a1, typeof(double));
                double b = (double)Convert.ChangeType(b1, typeof(double));

                double c = a + b;
                return (T)Convert.ChangeType(c, typeof(T));
            }
            else if (typeof(T) == typeof(decimal))
            {
                decimal a = (decimal)Convert.ChangeType(a1, typeof(decimal));
                decimal b = (decimal)Convert.ChangeType(b1, typeof(decimal));

                decimal c = a + b;
                return (T)Convert.ChangeType(c, typeof(T));
            }

            return default(T);
        }
    }
}

那怎么去看 Calculator<int></int>Calculator<long> </long> 到底变成啥了呢? 大家应该知道,C# 和 操作系统 隔了一层 C++,所以研究这种远离操作系统的语言还是有一点难度的,不过既然隔了一层 C++ ,那在 C++ 层面上必然会有所反应。

如果你熟悉 CLR 的类型系统,应该知道 C# 所有的 类 在其上都有一个 MethodTable 类来承载,所以它就是鉴别我们是否生成多个个体的依据,接下来我们用 WinDbg 查看托管堆,看看在其上是如何呈现的。


0:008> !dumpheap -stat
Statistics:
              MT    Count    TotalSize Class Name
00007ff9d37638e0        1           24 ConsoleApp1.Calculator1[[System.Int64, System.Private.CoreLib]]
00007ff9d3763800        1           24 ConsoleApp1.Calculator1[[System.Int32, System.Private.CoreLib]]

从输出信息看,C++ 层面变成了两个 methodtable 类,如果不信的化,还可以分别查看 mt 下的所有方法。

`c#

0:008> !dumpmt -md 00007ff9d37638e0
MethodDesc Table
Entry MethodDesc JIT Name

00007FF9D36924E8 00007ff9d37638d0 JIT ConsoleApp1.Calculator1[[System.Int64, System.Private.CoreLib]]..ctor()
00007FF9D36924E0 00007ff9d37638c0 JIT ConsoleApp1.Calculator
1[[System.Int64, System.Private.CoreLib]].getsum(Int64, Int64)

0:008> !dumpmt -md 00007ff9d3763800

Original: https://www.cnblogs.com/huangxincheng/p/16384668.html
Author: 一线码农
Title: 聊聊 C# 和 C++ 中的 泛型模板 底层玩法

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/536383/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • C++类中静态数据成员MAP如何初始化

    cpp;gutter:true; conv_xxx.hpp</p> <p>class convolution { …</p> <pre…

    C++ 2023年5月29日
    061
  • 【C++服务端技术】消息队列

    ThreadWorkUnit.h #pragma once #include #include #include "SafeQueue.h" namespace…

    C++ 2023年5月29日
    061
  • [c++] 拷贝构造函数

    拷贝构造函数就是进行对象拷贝复制的函数。 拷贝构造函数也是一种构造函数。它用同类型的对象来初始化新创建的对象。其唯一的形参是const类型&,此函数也由系统自动调用。 拷贝…

    C++ 2023年5月29日
    059
  • EclipseC++学习笔记-6 自动补头文件

    在报错代码处source->add Include 本博客是个人工作中记录,遇到问题可以互相探讨,没有遇到的问题可能没有时间去特意研究,勿扰。另外建了几个QQ技术群:2、全栈…

    C++ 2023年5月29日
    060
  • [转]C++ 模板 静态成员 定义(实例化)

    如果有这样一个模板: 对于以下若干种定义方式,哪些是对的(通过编译)? 为了不影响大家分析判断,我把答案颜色调成比较浅的颜色,下面即是答案: 首先,说明一下三个正确的答案。 第一种…

    C++ 2023年5月29日
    090
  • 聊聊 C++ 右值引用 和 移动构造函数

    一: 背景 最近在看 C++ 的右值引用和移动构造函数,感觉这东西一时半会还挺难理解的,可能是没踩过这方面的坑,所以没有那么大的深有体会,不管怎么说,这一篇我试着聊一下。 二: 右…

    C++ 2023年5月29日
    063
  • QT调用Delphi生成的COM组件(这样MinGW可以调用VC++做的COM了,有利于对接大厂只提供VC++ SDK的情况,也可转换lib)

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    C++ 2023年5月29日
    060
  • C++调用C#的动态库dll

    以往我们经常是需要使用C#来调用C++的dll,这通过PInvoke就能实现。现在在实际的项目过程中,有时会遇到在C++的项目中调用某个C#的dll来完成特定的某个功能,我们都知道…

    C++ 2023年5月29日
    091
  • c++调用tflite实战

    一,概述 深度学习模型在移动端的应用越来越多,tensorflow lite就是专门为tensorflow模型在移动端上线推断设计的框架。tensorflow 官方提供了不少cv的…

    C++ 2023年5月29日
    062
  • C++ 有向图最短路径之Dijkstra算法

    摘自:https://blog.csdn.net/chuanzhouxiao/article/details/88831371 一、思路 1.Dijkstra算法 每次都是从起始顶…

    C++ 2023年5月29日
    0107
  • C++应该更多使用堆还是栈?

    栈,是不需要涉及内存分配的,你可以把它看成一个很长的连续内存,用来执行函数。自动以先进后出的方式使用。具体的进出在C++里你可以假设是不能操纵这个栈的,实际上它存在。 _main函…

    C++ 2023年5月29日
    062
  • C++函数模板template(模板函数)

    函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。 面向对象的继承和多态机制有效…

    C++ 2023年5月29日
    062
  • C++ 内存池 — C++ Memory Pool

    这是我翻译的文章,来自 Code Project, 原文作者: DanDanger2000. 原文链接: http://www.codeproject.com/cpp/Memory…

    C++ 2023年5月29日
    086
  • C++11 static_assert

    1。assert是动态断言,运行期检查,影响性能,故debug版本检查,release关闭。 2。C++11中引入了static_assert这个关键字,用来做编译期间的断言,因此…

    C++ 2023年5月29日
    055
  • 汉诺塔的c++实现

    void hanNuoTa(int n,int a,int b,int c) { if (n == 0) return; hanNuoTa(n – 1, a, c, b); cou…

    C++ 2023年5月29日
    065
  • 逆向初级-C++(三)

    1、什么是封装:将函数定义到结构体内部,就是封装。2、什么是类:带有函数的结构体,称为类。3、什么是成员函数:结构体里面的函数,称为成员函数。 #include #include …

    C++ 2023年5月29日
    066
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球