• C11简洁之道:模板改进


    1、  右尖括号

      我们在C++98/03中使用泛型编程的时候,经常遇到“>>”被当作右移操作符,而不是模板参数的结尾。假如我们有如下代码:

    template <typename T>
    class Foo{};
    
    void func(void)
    {
        vector<Foo<int>> test;
    }

      使用C++98/03的时候会出现如下错误:

    hello.c:13:16: error: ‘>>’ should be ‘> >’ within a nested template argument list
    vector<Foo<int>> test;

      意思就是vector<Foo<int>>的写法不支持,我们不得不改成vector<Foo<int> >,”>>”之间有个空格。在C++11中对”>>”做了单独处理,是编译器知道“>>”是一个右移操作符还是模板结束符。不过,也有特例。如下代码在C++11中就会报错:

    template <int T>
    class Foo{};
    
    void func(void)
    {
        vector<Foo<100 >> 2>> test;
    }

      报错内容为:

    hello.c:13:20: error: expected unqualified-id before numeric constant
    vector<Foo<100 >> 2>> test;

      也就是产生了二义性,那么如何解决呢?加上括号就OK。

    vector<Foo<(100 >> 2)>> test;

    2、  模板别名

      在c++98/03中,我们可以通过typedef来定义一个类型的别名,当然,并不是产生新的类型,而是一个别名而已。像这样:

    typedef unsigned int uint;

      因为是别名,所以并不能通过typedef来进行函数的重载,像这样:

    typedef unsigned int uint;
    void func(unsigned int);
    void func(uint);              //error:erdefinition

      那么我们假如有如下需求,我们看C++98/03是怎么实现的:

      我们需要一个map,固定的key值为string,value为string或者int。

      正常定义是两种类型:

    typedef map<string, int> map_s_i;
    typedef map<string, string> map_s_s;

      那如果我们用模板来解决呢?

    template <typename Val>
    struct str_map
    {
        typedef map<string, Val> type;
    };
    str_map<int>::type map1;

      好,现在有了C++11,我们来看C++11是怎么实现的:

    template <typename Val>
    using str_map_t = map<string, Val>;
    
    str_map_t<int> map1;

      是不是很简单?语法也比C++98/03要简单太多,代码也简洁了不少。

      这里我们使用了C++11的关键字using,using几乎涵盖了typedef的所有功能。typedef的定定义方法和变量的声明类似,就像声明一个变量,在变量之前加上重定义类型,然后加上typedef即可;而using则不一样,using后面接新标识符,而后像赋值一样将需要别名的类型赋值给新标识符即可。总的来说,typedef有时候阅读难度比using更大(比如在定义函数指针的时候),using别名语法比typedef更加清晰。

    //重定义unsigned int
    typedef unsigned int uint_t;
    using uint_u = unsigned int;
    
    //重定义map
    typedef map<string, int> map_s_i_t;
    using map_s_i_u = map<string, int>;
    
    //定义函数指针
    typedef void (*func_t)(int, int);
    using func_u = void(*)(int, int);

      需要注意的是,using也不会产生新类型,上面的例子中using只是typedef的等价物,目的是改善C++98/03含蓄的语法,使代码可读性更高,更简洁。using同样不能重定义函数:

    using uint = unsigned int;
    void func(unsigned int);
    void func(uint);              //error:erdefinition

      值得注意的是,using定义的模板,既不是类模板,也不是函数模板,而是一种新的模板表达方式:模板别名。通过using可以轻松定义任意类型的模板表达方式。

    template<typename T>
    using u_t = T;
    
    u_t<int> i_int;

      u_t实例化后的类型和它的模板参数类型等价,这里u_t<int>等价于int。

    3、  函数模板的默认模板参数

    3.1 C++11的默认参数

      c++98/03中,类模版可以有默认模板参数,但是函数模板不支持默认参数。

    template<typename T, typename U = int, U N = 0>
    struct Foo
    {
        //
    }
    
    template <typename T = int> //error in c++98/03:default template arguments
    void func()
    {
        //
    }

      但是在C++11中解除了这个限制,上面的函数可以直接调用,就像调用正常函数一样:

    int main(void)
    {
        func();
     
        return 0;
    }

      当所有的类型都有默认的参数外,函数的调用就像一般的函数调用一样,但是类模板即使所有的类型都有参数,还是需要加上”<>”来实例化。

    3.2 细节

      模版默认参数可以和自动推导结合使用来提升灵活性。我们可以在平时的编码中,指定一部分模板参数,另外的一部分自动推导,但是有两条规则:

      如果编译器无法自动推导函数模板参数类型,则使用默认模板参数;

      如果能推导函数模板参数,则使用推导类型。

      我们来看两个例子:

    template<typename T>
    struct identity
    {
        typedef T type;
    };
     
    template <typename T = int>
    void func(typename identity<T>::type va, T = 0)
    {
        //...
    }
    
    int tfunc(void)
    {
        func(123);  //T->int
        func(123, 123.0); //T->double
    
        return 0;
    }

      通过identity模板禁用了val的类型自动推导,但是func指定了模板参数T的默认参数类型,所以func(123)使用默认的int类型,而在func(123,123.0)中,第二个参数123.0为double类型,所以T优先被推导为double类型。

  • 相关阅读:
    前端面试攻略1------算法部分
    MongoDB学习
    MongoDB作为Windows服务来安装 错误1053:服务没有及时响应启动或控制请求
    Vue 入门之 Vuex 实战
    Vue 路由详解
    vue入门全局配置
    VSCode配合ESLint自动修复格式化
    vue入门之单文件组件
    Echarts图表-学习
    GoJS学习
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/7648254.html
Copyright © 2020-2023  润新知