聊聊 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++接口设计和代码重构

    讲了接口设计, 文档(doxygen),测试等方面。对于从头编写基础库, 或者没有基础库开发经验并且需要短期内上手, 有一定实际参考价值。 所谓Mikado Method方法是用来…

    C++ 2023年5月29日
    068
  • ProtoBuf3 C++使用篇

    protobuf 是用于结构化数据串行化的灵活、高效、自动化的解决方案。又如 XML,不过它更小、更快、也更简单。你只需要按照你想要的数据存储格式编写一个.proto,然后使用生成…

    C++ 2023年5月29日
    063
  • c++使用用空格拼接字符串_accumulate用法

    std::accumulate(std::begin(x), std::end(x), string(), [](string &ss, string &s){re…

    C++ 2023年5月29日
    085
  • c++ 智能指针的向下转换 向下塑型 cast

    class A { public: void test() { std::cout << "test" << std::endl; } …

    C++ 2023年5月29日
    085
  • visual studio code的c++扩展

    posted @2020-08-20 10:07 kissrule 阅读(338 ) 评论() 编辑 Original: https://www.cnblogs.com/longc…

    C++ 2023年5月29日
    0116
  • c++builder调用VC的dll以及VC调用c++builder的dll

    解析__cdecl,__fastcall, __stdcall 的不同:在函数调用过程中,会使用堆栈,这三个表示不同的堆栈调用方式和释放方式。比如说__cdecl,它是标准的c方法…

    C++ 2023年5月29日
    079
  • C++11 并发指南三(Lock 详解)

    C++11 标准为我们提供了两种基本的锁类型,分别如下: std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。 std::unique_lock,…

    C++ 2023年5月29日
    083
  • 对指针和引用的理解(c++)

    1.指针 typedef说明一种新类型名,来代替已有类型名。 a.案例:typedef char String_t和#define String_d char 这两句在使用上的区别…

    C++ 2023年5月29日
    073
  • [C++] 引用

    引用的特点 通常意义上的引用是”左值引用”,(相对于右值引用,即 rvalue reference)。 引用是语法糖,变量别名。声明一个引用,不是新定义了一…

    C++ 2023年5月29日
    062
  • C++ 获取当前时间毫秒数

    在window环境下:1、精确到毫秒 include “stdafx.h” include include Original: https://www.cn…

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

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

    C++ 2023年5月29日
    068
  • C/C++标准新特性简介

    参考文档 C语言的起源发展 C语言诞生于1972年,美国贝尔实验室。作者为:Dennis MacAlistair Ritchie(丹尼斯·里奇) & Kenneth Lan…

    C++ 2023年5月29日
    090
  • c++ 头文件相互包含导致编译问题

    根本原因是用到某个符号的时候符号还没声明,找不到符号导致编译报错 方法是make .. verbose=1,展示所有预处理,编译等详细过程 然后使用 gcc -E ,查看文件包含展…

    C++ 2023年5月29日
    059
  • C++:vector中的resize()函数 VS reserve()函数

    http://www.cplusplus.com/reference/vector/vector/vector/ 写代码的时候无意错用了这两个函数 导致测试的时候,程序运行崩溃 发…

    C++ 2023年5月29日
    076
  • C++面试题1

    1,LeetCode给出一个 32 位的有符号整数,将这个整数中每位上的数字进行反转; 2,怎么判断一个变量是指针; Original: https://www.cnblogs.c…

    C++ 2023年5月29日
    067
  • How to: Create a C/C++ Union by Using Attributes (C#)

    【 How to: Create a C/C++ Union by Using Attributes (C#)】 1、you can create what is known as…

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