详解C++11中模板的优化问题

1. 模板的右尖括号

在泛型编程中,模板实例化有一个非常繁琐的地方,那就是连续的两个右尖括号(>>)会被编译器解析成右移操作符,而不是模板参数表的结束。我们先来看一段关于容器遍历的代码,在创建的类模板 Base 中提供了遍历容器的操作函数 traversal():

// test.cpp
#include <iostream>
#include <vector>
using namespace std;

template <typename T>
class Base
{
public:
    void traversal(T& t)
    {
        auto it = t.begin();
        for (; it != t.end(); ++it)
        {
            cout << *it << " ";
        }
        cout << endl;
    }
};

int main()
{
    vector<int> v{ 1,2,3,4,5,6,7,8,9 };
    Base<vector<int>> b;
    b.traversal(v);

    return 0;
}

如果使用 C++98/03 标准来编译上边的这段代码,就会得到如下的错误提示:

test.cpp:25:20: error: '>>' should be '> >' within a nested template argument list
Base<vector<int>> b;

根据错误提示中描述模板的两个右尖括之间需要添加空格,这样写起来就非常的麻烦,C++11改进了编译器的解析规则,尽可能地将多个右尖括号(>)解析成模板参数结束符,方便我们编写模板相关的代码。

上面的这段代码,在支持 C++11 的编译器中编译是没有任何问题的,如果使用 g++ 直接编译需要加参数 -std=c++11

2. 默认模板参数

在 C++98/03 标准中,类模板可以有默认的模板参数:

#include <iostream>
using namespace std;

template <typename T = int, T t = 520>
class Test
{
public:
    void print()
    {
        cout << "current value: " << t << endl;
    }
};

int main()
{
    Test<> t;
    t.print();

    Test<int, 1024> t1;
    t1.print();

    return 0;
}

但是不支持函数的默认模板参数,在C++11中添加了对函数模板默认参数的支持:

#include <iostream>
using namespace std;

template <typename T = int, T t = 520>
class Test
{
public:
    void print()
    {
        cout << "current value: " << t << endl;
    }
};

int main()
{
    Test<> t;
    t.print();

    Test<int, 1024> t1;
    t1.print();

    return 0;
}

通过上面的例子可以得到如下结论:当所有模板参数都有默认参数时,函数模板的调用如同一个普通函数。但对于类模板而言,哪怕所有参数都有默认参数,在使用时也必须在模板名后跟随 <> 来实例化。

另外:函数模板的默认模板参数在使用规则上和其他的默认参数也有一些不同,它没有必须写在参数表最后的限制。这样当默认模板参数和模板参数自动推导结合起来时,书写就显得非常灵活了。我们可以指定函数模板中的一部分模板参数使用默认参数,另一部分使用自动推导,比如下面的例子:

#include <iostream>
#include <string>
using namespace std;

template <typename R = int, typename N>
R func(N arg)
{
    return arg;
}

int main()
{
    auto ret1 = func(520);
    cout << "return value-1: " << ret1 << endl;

    auto ret2 = func<double>(52.134);
    cout << "return value-2: " << ret2 << endl;

    auto ret3 = func<int>(52.134);
    cout << "return value-3: " << ret3 << endl;

    auto ret4 = func<char, int>(100);
    cout << "return value-4: " << ret4 << endl;

    return 0;
}

测试代码输出的结果为:

return value-1: 520
return value-2: 52.134
return value-3: 52
return value-4: d

根据得到的日志输出,分析一下示例代码中调用的模板函数:

auto ret = func(520);
函数返回值类型使用了默认的模板参数,函数的参数类型是自动推导出来的为 int 类型。
auto ret1 = func<double>(52.134);
函数的返回值指定为 double 类型,函数参数是通过实参推导出来的,为 double 类型
auto ret3 = func<int>(52.134);
函数的返回值指定为 int 类型,函数参数是通过实参推导出来的,为 double 类型
auto ret4 = func<char, int>(100);
函数的参数为指定为 int 类型,函数返回值指定为 char 类型,不需要推导
当默认模板参数和模板参数自动推导同时使用时(优先级从高到低):

如果可以推导出参数类型则使用推导出的类型
如果函数模板无法推导出参数类型,那么编译器会使用默认模板参数
如果无法推导出模板参数类型并且没有设置默认模板参数,编译器就会报错。
看一下下面的例子:

#include <iostream>
#include <string>
using namespace std;

// 函数模板定义
template <typename T, typename U = char>
void func(T arg1 = 100, U arg2 = 100)
{
    cout << "arg1: " << arg1 << ", arg2: " << arg2 << endl;
}

int main()
{
    // 模板函数调用
    func('a');
    func(97, 'a');
    // func(); //编译报错
    return 0;
}

程序输出的结果为:

arg1: a, arg2: d
arg1: 97, arg2: a

分析一下调用的模板函数 func():

func('a'):参数 T 被自动推导为 char 类型,U 使用的默认模板参数为 char 类型
func(97, 'a');:参数 T 被自动推导为 int 类型,U 使用推导出的类型为 char
func();:参数 T 没有指定默认模板类型,并且无法自动推导,编译器会直接报错
模板参数类型的自动推导是根据模板函数调用时指定的实参进行推断的,没有实参则无法推导
模板参数类型的自动推导不会参考函数模板中指定的默认参数。

到此这篇关于详解C++11中模板的优化问题的文章就介绍到这了,更多相关C++11模板优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++11新特性之变长参数模板详解

    目录 C++11 变长参数模板 变长函数参数包 如何解参数包 sizeof()获得函数参数个数 递归模板函数 变参模板展开 结论 C++11 变长参数模板 在C++11之前,无论是类模板 还是函数模板,都只能按其指定的样子,接受一组固定数量的模板参数: 这已经大大提升了代码的复用! 在C++11之后,加入了新的表示方 法,允许任意个数.任意类别的模板参数,同时也不需要在定义时将参数的个数固定.更加像"黑魔法"了. template<typename... Ts> class

  • 一篇文章让你彻底明白c++11增加的变参数模板

    目录 前言 1. 什么是变参数模板 2. 变参数模板的基础-模板形参包 2.1 非类型模板形参包 2.2 类型模板形参包 2.3 模板模板形参包 3. 模板形参包的延伸-函数形参包 4. 模板形参包的展开方法 5. stl中使用模板形参包的案例 总结 前言 本篇文章介绍一下c++11中增加的变参数模板template<typename... _Args>到底是咋回事,以及它的具体用法. 说明一下,我用的是gcc7.1.0编译器,标准库源代码也是这个版本的. 按照惯例,还是先看一下本文大纲,如下

  • C++11模板元编程-std::enable_if示例详解

    C++11中引入了std::enable_if函数,函数原型如下: template< bool B, class T = void > struct enable_if; 可能的函数实现: template<bool B, class T = void> struct enable_if {}; template<class T> struct enable_if<true, T> { typedef T type; }; 由上可知,只有当第一个模板参数为

  • C++11 模板参数的“右值引用”是转发引用吗

    在C++11中,&&不再只有逻辑与的含义,还可能是右值引用: void f(int&& i); 但也不尽然,&&还可能是转发引用: template<typename T> void g(T&& obj); "转发引用"(forwarding reference)旧称"通用引用"(universal reference),它的"通用"之处在于你可以拿一个左值绑定给转发引用

  • 详解C++11 变参模板

    1.概述 变参模板(variadic template)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数.任意类型的参数.相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进.然而由于可变模版参数比较抽象,使用起来需要一定的技巧,掌握也存在一定的难度. 2.可变模版参数的展开 可变模板参数和普通模板参数的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号"-"

  • 详解c++11新特性之模板的改进

    C++11关于模板有一些细节的改进: 模板的右尖括号 模板的别名 函数模板的默认模板参数 模板的右尖括号 C++11之前是不允许两个右尖括号出现的,会被认为是右移操作符,所以需要中间加个空格进行分割,避免发生编译错误. int main() { std::vector<std::vector<int>> a; // error std::vector<std::vector<int> > b; // ok } 这个我之前都不知道,我开始学编程的时候就已经是C

  • 详解C++11中模板的优化问题

    1. 模板的右尖括号 在泛型编程中,模板实例化有一个非常繁琐的地方,那就是连续的两个右尖括号(>>)会被编译器解析成右移操作符,而不是模板参数表的结束.我们先来看一段关于容器遍历的代码,在创建的类模板 Base 中提供了遍历容器的操作函数 traversal(): // test.cpp #include <iostream> #include <vector> using namespace std; template <typename T> class

  • 详解C++11中的线程库

    目录 一.线程库的介绍 1.1. 使用时的注意点 1.2. 线程函数参数 1.3. join与detach 二.原子性操作库 2.1. atomic 2.2. 锁 三.使用lambda表达式创建多个线程 四.条件变量 一.线程库的介绍 在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差.C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念.要使用标

  • 详解C++11中的线程锁和条件变量

    线程 std::thread类, 位于<thread>头文件,实现了线程操作.std::thread可以和普通函数和 lambda 表达式搭配使用.它还允许向线程的执行函数传递任意多参数. #include <thread> void func() { // do some work } int main() { std::thread t(func); t.join(); return 0; } 上面的例子中,t是一个线程实例,函数func()在该线程运行.调用join()函数是

  • 详解Django项目中模板标签及模板的继承与引用(网站中快速布置广告)

    Django项目中模板标签及模板的继承与引用 常见模板标签 {% static %} {% for x in range(x) %}{% endfor %} 循环的序号{% forloop %} 循环的序号反向排列,从1开始计算,从0开始计算在后面加上0{% forloop.revcounter0 %} {% if condition1 %}sentence1{% else condition2 %}sentence2{% endif %} 模板标签url反向解析 视图函数 def studen

  • 详解C++11中的lambda匿名函数

    目录 1. lambda匿名函数的定义 lambda匿名函数中的[外部变量] 最简单的lambda匿名函数 2. lambda匿名函数的使用 2.1 lambda匿名函数的定义和使用 2.2 值传递和引用传递的区别 2.3 执行抛出异常类型 lambda 源自希腊字母表中第 11 位的 λ,在计算机科学领域,它则被用来表示一种匿名函数.所谓匿名函数,简单地理解就是没有名称的函数,又常被称为 lambda 函数或者 lambda 表达式. 1. lambda匿名函数的定义 [capture](pa

  • 详解C++11中的右值引用与移动语义

    C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

  • C++11中模板隐式实例化与显式实例化的定义详解分析

    目录 1. 隐式实例化 2. 显式实例化声明与定义 3. 显式实例化的用途 1. 隐式实例化 在代码中实际使用模板类构造对象或者调用模板函数时,编译器会根据调用者传给模板的实参进行模板类型推导然后对模板进行实例化,此过程中的实例化即是隐式实例化. template<typename T> T add(T t1, T2) { return t1 + t2; } template<typename T> class Dylan { public: T m_data; }; int ma

  • 详解为什么Vue中不要用index作为key(diff算法)

    前言 Vue 中的 key 是用来做什么的?为什么不推荐使用 index 作为 key?常常听说这样的问题,本篇文章带你从原理来一探究竟. 另外本文的结论对于性能的毁灭是针对列表子元素顺序会交换.或者子元素被删除的特殊情况,提前说明清楚,喷子绕道. 本篇已经收录在 Github 仓库,欢迎 Star: https://github.com/sl1673495/blogs/issues/39 示例 以这样一个列表为例: <ul> <li>1</li> <li>

  • 详解C++11原子类型与原子操作

    1.认识原子操作 原子操作就是在多线程程序中"最小的且不可并行化的"操作,意味着多个线程访问同一个资源时,有且仅有一个线程能对资源进行操作.通常情况下原子操作可以通过互斥的访问方式来保证,例如Linux下的互斥锁(mutex),Windows下的临界区(Critical Section)等.下面看一个Linux环境使用POSIX标准的pthread库实现多线程下的原子操作: #include <pthread.h> #include <iostream> usi

随机推荐