TH库学习: C语言实现模板编程(预备知识)

版权声明:本文为博主原创文章,如若转载,请注明出处! https://blog.csdn.net/u013010889/article/details/79628506

提前声明下面的例子和部分代码来源于PyTorch源码浅析(一)

引子

假如我们要实现一个函数: 两个Vector的相加,我们需要考虑int、float、double这3种类型,在C++中我们可以利用模板轻松搞定

// C++模板类,轻松搞定
template<typename T>
void add(Vector<T> &c, Vector<T> &a, Vector<T> &b)
{
 for(int i=0; i<a.size(); i++)
 {
   c.data[i] = a.data[i] + b.data[i];
 }
}
// C的话需要定义多个函数
void IntVector_add(int *c, int *a, int *b, int size)
{
 for(int i=0; i<size; i++)
 {
   *(c+i) = *(a+i) + *(b+i);
 }
}
void Float_add(float *c, float *a, float *b, int size)
{
 for(int i=0; i<size; i++)
 {
   *(c+i) = *(a+i) + *(b+i);
 }
}
// ......
// 这段代码完全没有复用,大大增加了后期维护成本。
// 如果再有个相减、相乘的操作,又要写这么多个函数

C语言宏应用

虽然C里面没有模板这种概念,我们只能借助它强大的宏来实现模板。我们的思路是用带参数的宏,操作名和操作数类型都作为宏参数,由宏展开来生成这些函数
用#把宏参数变为一个字符串,用##把两个宏参数拼接在一起.

// 最朴素的做法,NAME是操作名,Num为操作数类型
// 但是以下做法不行,因为在宏中出现##或#时,Num不会被展开为Float,而会被当初字符串"Num"
// 我们需要做个中转
# define Vector_(NAME) Num ## Vector_ ## NAME
# define Vector Num ## Vector

#define num float
#define Num Float

struct Vector
{
 num *data;
 int n;
};

void Vector_(add)(Vector *C, Vector *A, Vector *B)
{
//codes
}

正确的做法

#define CONCAT_2_EXPAND(A, B) A ## B
#define CONCAT_2(A, B) CONCAT_2_EXPAND(A, B)
#define CONCAT_3_EXPAND(A, B, C) A ## B ## C
#define CONCAT_3(A, B, C) CONCAT_3_EXPAND(A, B, C)

#define Vector_(NAME) CONCAT_3(Num, Vector_, NAME)
#define Vector CONCAT_2(Num, Vector)

#define num float
#define Num Float

struct Vector
{
 num *data;
 int n;
};

void Vector_(add)(Vector *C, Vector *A, Vector *B)
{
//codes
}
/*
Vector_(add) 首先代入宏定义 
-> Vector_(NAME) CONCAT_3(Float, Vector_, add)
-> CONCAT_3(Float, Vector_, add) CONCAT_3_EXPAND(Float, Vector_, add)
-> CONCAT_3_EXPAND(Float, Vector_, add) Float ## Vector_ ## add
-> FloatVector_add

Vector 代入宏定义 
-> Vector CONCAT_2(Float, Vector)
-> CONCAT_2(Float, Vector) CONCAT_2_EXPAND(Float, Vector)
-> CONCAT_2_EXPAND(Float, Vector) Float##Vector
-> FloatVector
看到中间有一步重复了
#define Vector CONCAT_2(A, B)
#CONCAT_2 A##B
也是可以的,但是有时候会直接使用CONCAT_2这个宏,为了防止出现有##宏不展开的问题,就多中转了一下
(直接定义 #define Vector Num##Vector 不行,上面已经说过了有##在Num不展开了)
*/

进阶

上一步中我们只定义了float类型,如果定义int类型,又要抄很长一部分代码,

// 同上
#define num int
#define Num Int
// 同上

我们可以把add、sub等都分离到./generic/Math.h和./generic/Math.c中

// ./generic/Math.h
// 函数声明
void Vector_(add)(Vector *C, Vector *A, Vector *B);
void Vector_(sub)(Vector *C, Vector *A, Vector *B);
// ================================================
// ./generic/Math.c
// 函数实现
void Vector_(add)(Vector *C, Vector *A, Vector *B)
{
//codes
}
void Vector_(sub)(Vector *C, Vector *A, Vector *B)
{
//codes
}

./generic/general.h 用来保存公用的”工具”宏

#define CONCAT_2_EXPAND(A, B) A ## B
#define CONCAT_2(A, B) CONCAT_2_EXPAND(A, B)
#define CONCAT_3_EXPAND(A, B, C) A ## B ## C
#define CONCAT_3(A, B, C) CONCAT_3_EXPAND(A, B, C)

然后在./Math.h和./Math.c中

// 以下为./Math.h 
// ./Math.c与其类似,把其中的.h换为.c即可
#include "general.h"

#define Vector_(NAME) CONCAT_3(Num, Vector_, NAME)
#define Vector CONCAT_2(Num, Vector)

#define num float
#define Num Float
#include "generic/Math.h"
#undef num
#undef Num

#define num double
#define Num Double
#include "generic/Math.h"
#undef num
#undef Num

再进阶

我们不仅Math需要生成不同的类型,Vector也需要生成不同的类型的,那么我们要在Vector.h和Vector.c中要写一些和Math.h中重复的代码如#define num float #define Num Float。
如果我们把#include “generic/Math.h”这个也变成一个变量,然后把生成各种类型的代码抽离出来成
GENERIC_FILE作为这个变量,也用宏实现

// GenerateAllTypes.h
#ifndef THGenerateManyTypes
    #define THAllLocalGenerateManyTypes // 1
    #define THGenerateManyTypes         // 1
#endif
// ================================================
// #include "DoubleType.h" 可以抽离到新的h文件中
#define real double // long int64_t
#define Real Double  
// 因为下面要include GENERIC_FILE,方便调试要把文件名改为GENERIC_FILE,行号也从1重新开始
// 有疑问?为什么需要重新设置,不是include时自动跳到那个文件了嘛 调试信息没问题吧?
#line 1 GENERIC_FILE
/*
#line number "string"
这条指令将修改__LINE__符号的值,如果加上可选部分,还将修改__FILE__符号的值。
它通知预处理器从下一行的行号被重新设定为 number 所代表的数字。如果给出了可选部分"string",
预处理器就把它作为当前文件的名字。
*/
#include GENERIC_FILE

#undef real
#undef Real
// ================================================
// #include "IntType.h" 可以抽离到新的h文件中
#define real int
#define Real Int

#line 1 GENERIC_FILE
#include GENERIC_FILE

#undef real
#undef Real
#ifdef THAllLocalGenerateManyTypes
    #undef THAllLocalGenerateManyTypes  // 0
    #undef THGenerateManyTypes          // 0
    #undef GENERIC_FILE     // all_types的最后一行出现在这,这一句一定要,不能删
#endif

生成各种类型的操作

// ./Math.h
// 声明该文件名为GENERIC_FILE
#ifndef GENERIC_FILE
    #define GENERIC_FILE "generic/Math.h"
#else
#include "generic/Math.h"
#include "GenerateAllTypes.h"
// ================================================
// ./Math.c
#ifndef GENERIC_FILE
    #define GENERIC_FILE "generic/Math.c"
#else
#include "generic/Math.c"
#include "GenerateAllTypes.h"

其他需要生成各种类型的什么,比如生成各种类型的Tensor

// ./Tensor.h
// 声明该文件名为GENERIC_FILE
#ifndef GENERIC_FILE
    #define GENERIC_FILE "generic/Tensor.h"
#else
#include "generic/Tensor.h"
#include "GenerateAllTypes.h"
// ================================================
// ./Tensor.c
#ifndef GENERIC_FILE
    #define GENERIC_FILE "generic/Tensor.c"
#else
#include "generic/Tensor.c"
#include "GenerateAllTypes.h"

pytorch下的lib库 源码阅读笔记(1)
tiny_lib

阅读更多

扫码向博主提问

爆米花好美啊

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • deep learn
  • detection
  • segmentati
去开通我的Chat快问
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页