聊聊 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++基础 (杂七杂八的汇总 )

    各数据类型在32位系统和64位系统占的字节数: C类型 32 64 char 1 1 short int 2 2 int 4 4 long int 4 8 long long in…

    C++ 2023年5月29日
    060
  • Xcode 导出C++项目在其他电脑执行

    先找到C++项目的可执行文件的位置 https://blog.csdn.net/qq_34759481/article/details/82700587 关于存储和加载文件的目录,…

    C++ 2023年5月29日
    051
  • [C++] const和mutable关键字使用方法

    const 修饰的变量为常成员变量,表示此变量不能被修改赋值,并且构造函数中只能用初始化列表的方式初始化,其他初始化方式都是错误的 const 修饰的函数为常成员函数,表示此函数中…

    C++ 2023年5月29日
    058
  • VC++ 使用attributes定义接口

    1.定义预处理命令_ATL_ATTRIBUTES 2.在一个全局的Cpp文件里面配置module的attribute [module(dll, uuid = "{3845…

    C++ 2023年5月29日
    061
  • 您的第一个C++Builder程序(Hello, world!)

    最近有些老旧的项目是C++Builder开发的,虽然和Delphi的IDE的界面和操作十分相似,但是还是找本《C++ Builder 5 Developer’s Gui…

    C++ 2023年5月29日
    057
  • clang-format 对 c++ 进行格式化

    在 VS Code 中安装了 C/C++ 插件后会自动带上格式化工具 clang-format。按 option+shift+f 即可对文件进行 format(格式化)。 在目录下…

    C++ 2023年5月29日
    070
  • (筆記) 常用設定暫存器值的編程技巧 (SOC) (C/C++) (C) (Verilog)

    Abstract設定暫存器值是寫firmware時最常見的控制,本文歸納出C語言在寫firmware時常見的編程技巧,並與Verilog相互對照。 Introduction本文將討…

    C++ 2023年5月29日
    079
  • c++ 条件变量

    http://blog.csdn.net/hemmanhui/article/details/4417433 互斥锁:用来上锁。 条件变量:用来等待,当条件变量用来自动阻塞一个线程…

    C++ 2023年5月29日
    041
  • (转)C/C++中计算程序运行时间

    以前经常听人提起如何计算程序运行时间,给出一系列函数,当时没有注意,随便选了clock()最简单的方式进行计算。等到真正需要检测程序性能提升了多少,才发现这里面有很多要注意的地方。…

    C++ 2023年5月29日
    051
  • 【C++自绘控件】如何用GDI+来显示图片

    在我们制作一个应用软件的时候往往需要在窗口或控件中添加背景图。而图片不仅有BMP格式的,还有JPEG、PNG、TIFF、GIF等其它的格式。那么如何用jpg格式的图片来当背景呢? …

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

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

    C++ 2023年5月29日
    049
  • C++ CRTP

    CRTP 英:The curiously recurring template pattern (CRTP) is a C++ idiom in which a class X d…

    C++ 2023年5月29日
    080
  • (筆記) 如何寫入binary file某個byte的值? (C/C++) (C)

    Abstract通常公司為了保護其智慧財產權,會自己定義檔案格式,其header區會定義每個byte各代表某項資訊,所以常常需要直接對binary檔的某byte直接進行寫入。 In…

    C++ 2023年5月29日
    043
  • 【转】C++的赋值构造函数(赋值运算符重载)

    当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值构造函数。 当没有重载赋值构造函数(赋值运算符)时,通过默认赋值构造函数来进行赋值操作 注意:这里a,b对象是已经存在的,…

    C++ 2023年5月29日
    046
  • 右值引用与转移语义(C++11)

    参考资料: 左值和右值定义: C++( 包括 C) 中所有的表达式和变量要么是左值,要么是右值。通俗的 左值的定义就是非临时对象(可以取地址,有名字),那些可以在多条语句中使用的对…

    C++ 2023年5月29日
    065
  • C/C++知识点记录

    1. strcpy 的部分理解char strcpy(char dest, const charsrc){while(dest++=src++)return dest-1;}这是s…

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