模板的高级用法

一直以来都是通过C用基于对象的设计方法来写代码。即使工作中使用C++, 也是尽可能少的使用超出C的一些特性。当然这并不是C++不好,而是C++实在太复杂了。以我的脑力来讲, 如果使用C++过多的特性, 很容易让我过于陷入语法特性之中, 而忽略了设计。因此, 对于C++的一些高级特性, 如模板等并没有深入研究过。

模板对我来讲, 仅限于知道可以实现泛型。至于怎么巧妙的利用泛型来实现其他特性, 从来没有深入研究过。最近工作中,碰到了一些看起来比较高端的模板用法,令人有一种耳目一新的感觉,因此就记录一下。


如果实现一个单纯的组播模块, 该模块提供的组播接口可能类似:

void multicast(func, arg1, arg2, arg3, ...);

在不用模板的情况下,假设每种参数有可能有m种类型,如果参数的个数为n个。 那么针对参数为n个的函数就需要手写m^n个重载板本。

使用模板推导功能,对于参数个数为n个的函数,就仅仅只需要实现一个实现即可。

示意代码大概如下:

template
int call(void (*func)(T1 p1, T2 p2), T1 p1, T2 p2)
{
func(p1, p2);
}
void func1(int a, float b)
{
printf("%d, %f\n", a, b);
}
void func2(double a, const char *b)
{
printf("%lf, %s\n", a, b);
}
int main()
{
call(func1, 3, 3.5f);
call(func2, 5.3, "hello");
return 0;
}

这样使用模板忽略掉了参数类型,仅仅针对参数个数进行重载, 会大大提高代码的编写效率。


对于C++来讲,其实RAII应该算是惯用手段了。

在不使用模板的情况下,如果我们使用RAII来管理指针,我们就需要为每一个类实现一个指针类。而如果使用模板,仅仅实现一个指针类就可以了。

比如:

template
class PTR {
public:
PTR(T a) {
ptr = a;
};
~PTR() {
if (ptr == NULL)
return;
delete ptr;
};

T get() {
return ptr;
};
private:
T ptr;
};

class test {
public:
test() {printf("test\n");}
~test() {printf("~test\n");}
void hello() {printf("%s\n", h);}
void set(const char *n) {h = n;}
private:
const char *h;
};

int main()
{
PTR p(new test);
test *t = p.get();
t->set("hello");
t->hello();

return 0;
}

其实上面的代码大致就是C++11提供的std::unique_ptr实现的功能了.


在网络通信过程中,定义数据结构总是一件很烦的事,使用纯C的数据结构便于传输,但是会将结构定义的很死, 而且数据种类有限。

如果使用vector/unordered_map并且相互嵌套时传输就会很麻烦。一般这时候就不得不采用protobuf的方式来进行传输。

但是有时候你要传输的数据结构仅仅就是vector/unordered_map等动态数据结构的一些嵌套, 这时候去使用protobuf就稍嫌重量了。

这时其实可以结合C++的模板推导及参数重载来实现一个简易的数据序列化库。

大概试了一下vector,用起来还是很方便的。

template void
serial(const T &a, std::string &res)
{
res.append((char *)&a, sizeof(a));
}

template size_t
unserial(T &a, const char *p)
{
a = *(T *)p;
return sizeof(T);
}

template void
serial(const std::vector &a, std::string &res)
{
size_t n = a.size();
res.append((char *)&n, sizeof(n));
for (size_t i = 0; i < n; i++) serial(a[i], res); } template size_t
unserial(std::vector &a, const char *p)
{
size_t pos;
size_t n = *(size_t *)p;
a.reserve(n);
pos = sizeof(size_t);
for (size_t i = 0; i < n; i++) { T tmp; pos += unserial(tmp, &p[pos]); a.push_back(tmp); } return pos; } int main() { std::vector src1 = {1, 2, 3, 4, 99, 100};
std::vector src2;
std::string dst;

serial(src1, dst);
unserial(src2, &dst[0]);

for (size_t i = 0; i < src2.size(); i++) printf("%d ", src2[i]); return 0; }


从上面三个模板的例子上看,基本上模板就是强类型语言用来在写代码时弱化类型的一个折中。如果有过动态语言编写经验就会明显感觉到,模板明显是为了有限的支持动态语言在编写时的一些优点。如果能够把握这一点,也许才能够更合理,也更巧妙的去运用模板。

发表评论

eight + 1 =