第1章:C++泛型技术基础:模板——《C++泛型:STL原理和应用》读书笔记整理
第1章:C++泛型技术基础:模板
1.2 关于模板参数
1.2.1 模板参数类型
类型参数
typename声明的参数都属于类型参数,它的实参必须为系统内置或者用户自定义的数据类型,包括类模板实体,由类模板产生的类模板实体,本质上就是类。非类型参数
C++允许人们在模板参数列表中像函数参数列表中那样定义普通变量或者对象。定义的普通变量不能被修改,因为模板参数是在预编译期间进行传递并且被编译的。仅支持可以转换为Int类型的变量(double都不行!)、枚举、指针、引用。template<typename T, int b>
模板定义型参数
C++也允许以类模板的定义作为类模板参数,之所以需要这种参数,其目的就是除了强调这个参数的实参必须为类模板外,还强调这个类模板所具有的参数个数。//其中T就是一个单参数类模板 template<template<typename S>class T>
1.2.2 模板形参和实参的结合
- 函数模板实参的隐式提供
实参的隐式提供需要提供返回值的类型,或者可以使用auto+decltype大法推导返回值的类型。
- 指针实参
指针其实是类型参数的一种,使用4字节的存储空间。
- 修饰符const和&的使用
修饰符const和&只是对模板参数的修饰,对模板参数类型无太大影响
1.3 特化模板和模板具现规则
1.3.1 特化模板(特例化模板)
- 函数模板中的特化模板
没啥好说的就是普通的特化。不过模板函数也是可以偏特化的,比如一个双参数函数,是可以特化第一个参数的。 - 类模板的特化和偏特化
偏特化就是指特化一个模板参数。 - 模板的具现
在源文件中,我们可以使用多种方式编写一个泛型的程序功能模块,他可能是特化模板、偏特化模板以及全特化模板,或者这几种泛型模块共存。显然编译器需要一个模板具现的规则,优先生成哪个模板的实体。编译器对于一个模块调用的具现优先规则应为:
1.4 右值引用与模板
1.4.1 右值引用
左值值既可以出现在赋值运算符的左边和右边,右值只可以出现在赋值运算法的右边。左值之所以可以出现在赋值运算符的左边,就是因为这种表达式代表一块存储空间,可以接受并保存数据。程序可以通过其变量名获取地址,并用这个地址访问数据。右值仅能代表数据。右值表达式要么是数据本身,要么是一个能得出结果的运算表达式,尽管它也占据一定的存储空间,但是因为它没有名字,也不能从其表达式中提取这个空间的地址。因此这种表达式只能出现在赋值运算符的右边,而且仅能代表生命期与其所在语句相同的临时对象。
关于引用,在C++11之前,左值可以定义两种引用:左值常引用和左值引用。对于右值,C++11之前仅定义了一种常量引用。对右值进行常量引用可以延长右值的生命期,从而使后续程序可以利用它的信息,遗憾的就是它是常量,不能满足程序更多的要求。
为了能充分利用临时对象,C++11标准推出了一种新的数据类型——右值的非常量引用,简称右值引用。
T&& name = rvalue;
1.4.2 右值引用应用1——转移语义
深拷贝与浅拷贝:
#include <iostream>
using namespace std;
class Student {
private:
int num;
char *name;
public:
Student();
~Student();
};
Student::Student(){
name = new char(20);
cout << "Student" << endl;
}
Student::~Student(){
cout << "~Student " << (int)name << endl;
delete name;
name = NULL;
}
int main()
{
Student s1;
// 使用了默认拷贝函数,默认拷贝函数进行的是浅拷贝
// 因此在析构的时候会对同一个内存空间释放两次,造成内存泄漏。
Student s2(s1);
return 0;
}
浅拷贝优点是速度快,节省资源,缺点是共享了资源,容易引起内存泄漏。深拷贝不会引起泄漏,但是每次拷贝都消耗大量资源。
转移语义:
//关闭RVO,return value optimistic,返回值优化
//不关闭在返回右值的时候会跳过移动构造函数,直接构造对象。
g++ -fno-elide-constructors e1_b.cpp -o e1_b && ./e1_b
Foo fuct(){
Foo foof(100); //产生局部变量,生命周期只有在fuct()函数中,在函数返回的时候会被析构。
return foof; //这里其实是调用了Foo类的拷贝函数,拷贝了一份作为返回值。
}
fuct();
Foo(Foo&& r) //调用拷贝函数,返回一份拷贝
~Foo() //函数中的局部变量生命周期结束
~Foo() //函数返回值的生命周期结束,在主函数return的时候
//为什么会调用了一次析构函数呢?
//因为foo1的生命周期是整个主函数,少的那一次在主函数return的时候会被调用
//不是初始化!没有调用构造函数!声明了一个常引用接受fuct()的返回值。
const Foo& foo1 = fuct();
Foo(Foo&& r) //拷贝局部变量作为返回值
~Foo() //局部变量声明周期结束
//与上相同,只是一个是常引用,另一个是非常量引用
Foo&& foo2 = fuct();
Foo(Foo&& r)
~Foo()
//隐式调用构造函数和显式调用构造函数!
Foo foo3(fuct());
Foo foo3 = Foo(fuct());
Foo(Foo&& r) //拷贝作为返回
~Foo() //析构局部变量
Foo(Foo&& r) //调用拷贝构造函数,创建对象foo3,
~Foo() //返回值声明周期结束,调用析构函数
return 0!
在某些情况下,被拷贝的对象是右值,意味着其生命周期即将结束,此时如果我们再去消耗资源开辟新空间就显得浪费了。因为被拷贝对象即将”死亡“,我们不妨借用一下这个右值的内存空间!这就是语义转移,即使用浅拷贝共享内存空间后,将”强迫“原对象放弃资源控制权(指针为空),避免内存空间被释放。通过这种方式,原对象(右值)的内存空间,被我们转移(窃取)出来,用于新对象,通过这种方式,我们极大地节省了开销。
1.4.3 右值引用应用2——转移函数move()
看到了右值引用的好处,左值引用也想利益共沾。但是右值引用,必须参数要是右值才能完成语义转移,因为左值在逻辑上是不允许被窃取内存空间的。
T&& std::move(T&);
void swap(T& a, T& b){
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
1.4.4 右值引用应用3——参数完美转发模板
在程序设计实践中,函数模板经常需要调用一些外部函数实现它的功能,这时候就需要模板为这些函数传递参数,人们习惯上把模板的这种行为叫做参数转发。对于模板来说,它的任务就是参数的忠实转发,一不能改变参数特性,二不能产生额外开销。如果模板能把所有数据类型都按照上述要求进行转发,那么这个模板就是一个完美参数转发者。
//方案一:
void Func(int); //目标函数
template<typename T>
Tmp(T); //转发模板
//结果:这种方式在Tmp(10),参数为右值的时候,在经模板转发后变成了左值
//方案二:
void Func(int v);
template<typename T>
Tmp(T&);
//结果:无法转发右值,Tmp(100)报错,因为模板为左值引用
//改进,Tmp(const T&),此时模板转发后参数变为常引用,可能不符合某些函数要求
//方案三:
void Func(int& v);
template<typename T>
Tmp(T&& a){
Func(a);
}
//结果:C++11前无法通过,因为C++11前还没有右值引用T&&,以及对多个引用符&连用的数据类型进行推导的功能
/* 多重引用推导表
实参类型 模板参数 推导类型
int& T& int&
int&& T& int&
int& T&& int&
int&& T&& int&&
lvalue T&& T& 左值 + T&& = T&
rvalue T&& T 右值 + T&& = T(左值)
*/
//在这种推导情况下,如果Tmp的参数为右值,在转发后参数类型就会被推导为左值,这与我们的预期不同。
//对此我们在Tmp函数内对参数进行强制的类型转换
Tmp(T&& a){
Func(static_cast<T&&>(a));
}
//因为左值+T&& = T&&, T& + T&& = T&, 实现了参数的完美转发。
//为了区别于move()和static_cast,并使之更具有语义性,C++11将static_cast()封装在函数模板std:forward()。
Tmp(T&& a){
func(std::forward<T>(a));
}
原文地址:https://www.cnblogs.com/azhao/p/11881231.html
- 【 关关的刷题日记47】Leetcode 38. Count and Say
- 《Python自然语言处理》答案第一、二章
- 【 关关的刷题日记49】 Leetcode 434. Number of Segments in a String
- 自然语言处理构建文本向量空间1.百科2.源代码3.参考:
- 小爬虫之爬取豆瓣电影排行榜1.技术路线2.任务3.分析4.运行结果5.源码
- Numpy 修炼之道 (5)—— 索引和切片
- 深入理解final关键字
- Numpy 修炼之道 (4)—— 基本运算操作
- 一些APT攻击案例分享
- 浅谈命令查询职责分离(CQRS)模式
- Numpy 修炼之道 (3)—— 数据类型
- 熔断器设计模式
- 树链剖分详解
- 洛谷P3379 【模板】最近公共祖先(LCA)(树链剖分)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- (数据科学学习手札95)elyra——jupyter lab平台最强插件集
- Python 序列化/反序列化自定义类型
- Windows10下使用VS2017编译和使用yaml-cpp库
- CentOS7下编译yaml-cpp库
- SAP Spartacus CurrentProductService返回的null对象
- SAP Spartacus产品明细页面用Observable显示产品名称
- 关于rxjs里operators filter和map的详细讨论
- 用代码查看SAP Spartacus购物车内的行项目
- rxjs的map和switchMap在SAP Spartacus中的应用
- 用代码查看SAP Spartacus购物车内的行项目
- rxjs fromEvent的用法
- Python2和Python3的区别简单总结
- Django操作数据库
- Hive元数据服务MetaStore
- Linux---Shell脚本字符显示特殊颜色效果