C++模板使用小技巧7则

这篇文章涉及的内容已经不是什么新鲜玩意了。对于部分三角和全体星级的用户来说,这只是些小把戏。我从众多的小把戏中挑了几个出来说明了一下,也没有什么高深的概念,至于我写这个的目的,其实也没什么,闲来无事吧。


1 获得一个指针进行一次解引用之后的类型

 

指针在解引用一次之后会得到它引用对象的类型。我们可以用偏特化来得到这个类型。
template <typename    T >
struct    rm_a_ptr
{
typedef    T    value_type;
};

template <typename    T >
struct    rm_a_ptr <T* >
{
typedef    T    value_type;
};

rm_a_ptr <int* >::value_type    I    =    5;

当然我们可以获得移出所有ptr,只需要把这个偏特化变通一下。这里就不用讨论了。


2 static_if


static_if就是在编译期的时候进行条件控制。其实#ifXXX这些编译预处理器就是做这些事情的,不过static_if还有一个额外的动作就是让语言的类型系统也参与运算。

template <bool    Cond,    typename    TrueType,    typename    FalseType >
struct    static_if
{
typedef    TrueType    value_type;
};

template <typename    TrueType,    typename    FalseType >
struct    static_if <false,    TrueType,    FalseType >
{
typedef    FalseType    value_type;
};

用模板偏特化判断第一个非类型模板参数进而决定选用TrueType和FalseType。


3 判断两个类型是否相同。


C++提供了对函数的重载,编译器在一系列的重载函数中来寻找最佳匹配。这是我们实现类型判断的基本依据。为此我们需要两个函数来完成这种匹配。
template <typename    T >    int    match(T);
template <typename    T >    char    match(…);
这两个函数都可以接纳所有类型的参数,但是第一个match可以让它只接纳指定的参数类型,来看看下面的代码。
match <int >(1);
通过 <int >来指定模板参数T为int,那么第一个match的参数就是int,而1正是int,所以这里的最佳匹配是第一个match而不是第二个。基于这一点,我们就可以完成两个类型是否相同的判断了。

template <typename    T,    typename    U >
struct    same_type
{
enum{    value    =    (sizeof(int)    ==    sizeof(match <T** >((U**)0)))    };
};

match <T** >((U**)0)    发生了什么?
我们把空指针0显式转换成U**然后拿给match与T**匹配。如果U和T是同一个类型,则match返回值的类型是int。    如果U和T不是同一个类型,则匹配第二个match,其返回值类型就是char。所以把sizeof(int)的值和sizeof    match进行比较就可以把匹配的结果利用在编译期当中,然后再把这个值交由enum中的value来保存。

为什么不用match <T >(U())来进行匹配呢?因为如果U派生自T的话,那么匹配第一个match,尽管U和T的关系是is    a,但是在这种情况下把他们当作成不是一样的更好。不然下面的例子你会犯愁
class    A{};
class    B:    public    A{};

bool    x    =    same_type <A,    B >::value;
if(x    ==    same_type <B,    A >::value)    //永远都else.

那为什么要用二维指针而不直接是T*和U*的比较呢?原因其实和上面一样。


4 判断一个类型是不是函数类型。

 
数组有一个特点就是,元素的类型不能为函数类型和void。由此可以用以上的方法来实现这个判断
template <typename    T >    char    match_func_type(T(*)[2]);
template <typename    Function >    int    match_func_type(…);

在使用match_func_type的时候仍然要指定模板参数
match_func_type <void() >(0);
这会匹配第二个match_func_type,因为我们指定的模板参数是一个函数类型。由此可以看出,第一个match_func_type的参数会是一个指向函数类型数组的指针,显然这是不可能的,因为没有函数类型的数组。所以会匹配到第二个match_func_type。注意,我们还需要排除掉void类型。

template <typename    F >
struct    is_function_type
{
enum{value    =      (same_type <F,    void >::value    ==    0)    &&    (sizeof(int)    ==    sizeof(match_func_type <F >)(0))};
};
这里的表达式的伪码为
(F    !=    void    &&    match_func_type(…));


5 判断一个类型是不是函数指针


有了上面的功能,我们可以很快实现出这个。判断进行一次解引用之后的类型是不是函数类型就可以了。

template <typename    F >
struct    is_function_pointer
{
enum{value    =    is_function_type <typename    rm_a_ptr <F >::value_type >::value};
};


6 我们得到了什么?


上面通篇的论述和指导展示了C++模板的一些作用。T不仅仅只是一个容器这么简单。但是这些在实际工作中看似不会有什么作用,或者只是让代码变得更炫的一些把戏,甚至成为证明自己比别人高明的手段。到最后,开始质疑这些代码是否有必要出现。


7 !@#$%^&*()_+


LoadLibrary    +    GetProcAddress    +    FreeLibrary是一件烦人的事情,每次用都要判断句柄,因此在一段时间里,处理这部分我都会Copy&Paste以前的代码,然后作点小小的修改。最后发现Copy&Paste也是一件烦人的事情,所以我写了一个类把这个工作给封装了。
class    shared_wrapper
{
public:
shared_wrapper(const    char*    filename);
~shared_wrapper();
bool    empty();
void*    symbols(const    char*    symbol);
private:
HMODULE    module_;
};

构造函数和析构函数分别负责LoadLibrary和FreeLibrary这是显而易见的。empty()返回true表示指定的动态库加载成功了。symbols则返回接口的地址。定义如下
void*    shared_wrapper::symbols(const    char*    symbol)
{
if(empty())
throw      my_excep( "shared_wrapper.symbols,    empty    shared    library ");

if(0    ==    symbol)
throw    my_excep( "shared_wrapper.symbols,    null    symbol ");

void*    result    =    ::GetProcAddress(module_,    symbol);
if(0    ==    result)
{
std::string    what    =    "shared_wrapper.symbols,    no    symbol    named    ";
what    +=    symbol;
throw    my_excep(what.c_str());
}

return    result;
}

后来感觉symbols返回一个void*特不爽。干嘛不让它返回一个我们直接可以用的函数指针呢?于是我把它改成了这样
template <typename    FuncPtr >
FuncPtr    symbols(const    char*    filename)
{
//…
return    (FuncPtr)result;
}

后来再一次编码过程中,顺手写了下面这行代码
Shared.symbols <void(int) >( "interface ")(5);
后来编译错误告诉我,我返回了一个函数。这时我顿悟,    symbols应该接纳一个函数类型而不仅仅只接纳函数指针类型。因为最后symbols都应该返回的是一个函数指针,而指定函数类型已经提供了足够的类型信息了。所以我再次对symbols动了手脚。

现在symbols的模板参数可以是函数类型,也可以是函数指针类型。于是先写一个辅助类模板来计算该模板参数的函数指针类型方便后面使用。

template <typename    T >
struct    make_func_ptr
{
typedef    typename    rm_a_ptr <T >::value_type    prototype;

typedef    typename    static_if <is_function_type <prototype >::value,    prototype*,    int >::value_type    value_type;
};

在这里,我们可以把static_if理解成下面的伪码
prototype    =    typeof(*T);
if(prototype    ==    function    type)
return    &prototype;
else
return    int;

后面为什么会return    int呢?只是借用一下,只要不return    函数指针类型就行了,下面会看到return    int的作用。

Template <typename    Function >
typename    make_func_ptr <Function >::value_type
symbols(const    char*    filename)
{
typedef    typename    make_func_ptr <Function >::value_type    fptr_type;
if(is_function_pointer <fptr_type >::value    ==    0)
throw    my_excep( "shared_wrapper.symbols,    template <Function >    is    not    a    function    type    or    a    function    pointer    type ");

//…
return    (fptr_type)result;
}

上面return    int的作用就在symbols里,如果return    int则fptr_type就是int,那么下面的if就会捕捉到这个错误。这就是最后的模样了。


8 重新看待这些够炫的小把戏。


用代码来实现一些语言未曾提供的特性,难道这不是灵活和强大的表现吗?总是看到一部分人说这没用、那没用,也许是他们没有考虑过这些该怎么用。应用是否得当一切都来自于对问题的把握和理解。各位的观点是什么?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值