2.1auto关键字
auto expr;
- 当expr包含cv描述符的时候,比如const int a = 1;auto b = a;此时b的类型为int,会丢失掉const类型
- const int a[10]; auto b = a; 此时b类型退化成int*
作为对比
- const int a= 1; auto &b = a; 此时b的类型为const int& 没有丢失const
- int a[10]; auto& b = a; 此时b类型为int(&)[3]
尝试用auto申明一个变量的时候是申明一个新的值,语义是复制,所以会丢失掉cv标识符或者数组,如果加了引用后表示引用之前的变量,所以自然不会丢失ref类型,初始化表达式如果是数组或者函数,那么也同理退化成指针,如果加了引用自然不会退回。
auto 函数返回值推倒类型
template<class T,class U>
? add(T t,U u) { return a + b}
可以这样
template<class T,class U>
auto add(T t,U u) { return a + b} auto类型为operator+(T,U)
普通函数
auto equaltOne(int x){
if(x==1)
return true;
else
return false;
}
编译器要求如果一个函数有多个return语句的时候,需要推倒成相同的类型
否则会类似error: inconsistent deduction for auto return type: 'bool' and then 'int'
在new表达式
auto p = new auto('c') // p is a char*
2.2decltype
-例子1
std::map<char,int> mymap;
mymap.insert(std::pair<char,int>('a',100));
mymap.insert(std::map<char,int>::value_type('z',101));
--上树两种写法hard code map的value type的具体类型
--这种写法不依赖mymap的具体类型
mymap.insert(decltype(mymap)::value_type('z',101))
-例子2
std::map<int,std::string> somemap;
std::map<std::string,int> remap;
想通过上述的map来获取下面的map,颠倒key和value的类型
也可以这么写
std::map<typename decltype(somemap)::mapped_type,typename decltype(somemap)::key_type> remap;
还可以抽象出来用个模板函数来生成
template<typename MapType>
auto RevertMap(MapType somemap) {
return std::map<typename decltype(somemap)::mapped_type,typename decltype(somemap)::key_type>();
}
decltype(expr) 返回一个表达式的类型
int x = 1;
//但是加括号多了引用类型
decltype((x)); //返回int&
//函数参数
struct A{};
A& fun();
decltype(func()); //A&
//++
int x=1;int y=2;
decltype(++x) //int& x的值并不会增加,因为编译器推倒的,而不是真的去执行
decltype(x+y) //int
注意 decltype(expr) 表达式并不会真的会执行,因为编译器推倒类型的,并不会真的被执行、
auto和decltype的区别个人理解是decltype可以通过一个表达式来获取类型,auto是直接推倒
2.3 decltype(auto)
//例子1
auto f(){
return g();
}
//g()可能返回T或者T&,由于aotu会去除cv描述符,所以退化成T
//例子2
//想要保留引用特性 所以得这么做
decltype(auto) f(){
return g();
}
//例子3
int x = 1;
int& rx = x;
auto rx1 = rx; //int
decltype(auto) rx2 = rx; //int&
3.1 move sementic 移动语义
struct MemoryBlock {
int *_data;
int _len;
MemoryBlock(int l);
//复制构造函数
MemoryBlock(const MemoryBlock&other):_data(NULL),_len(0){
_data = new int(other._len);
_len = other._len;
std::memcpy(_data,other._data,_len * sizeof(_len));
}
};
用法1
std::vector<MemoryBlock> vec;
MemoryBlock mb1(10);
vec.push_back(mb1);//复制构造函数
//delete mb1
//这个例子浪费了一个复制构造 一个析构
push back会resize,resize的过程中会先析构掉原来的item,然后在复制过来
这个背景下,所以复制构造在这里会变得不可忍受,还有有的对象不支持复制比如锁,不能被其他对象复制。
想实现移动语义,class要支持move语义,且需要指示编译器生成代码时调用定义的move操作
想支持移动构造的class T 需要满足以下特点
- 不能是个模板构造函数
- 第一个参数 T&&,const T&&,volatile T&& 或者const volatile T&&
- 剩下的参数都有默认值
class SomeClass{
SomeClass([CV] SomeClass&&); //是
SomeClass([CV] SomeClass&&,int parm1=1,...); //是
template<typename U> SomeClass(U&&); //not move actor
}
接着上述的MemoryBlock
//移动构造函数
MemoryBlock(MemoryBlock&*other):_data(NULL),_len(0){
_data = other._data;
_len = other._len;
other._data = NULL;
}
3.2指示编译器来调用移动构造
std::vector<MemoryBlock> vec;
MemoryBlock mb1(10);
vec.push_back(mb1); //copy ctor or move ctor 都支持 编译器怎么选呢?
3.2 value category
c++ 表达式通过2个部分的属性 一个type 和 一个value category
- LValue
- Prvalue
- Xvalue
常见的lvalue表达式
- 变量,数据成员,函数
- 如果函数返回一个lvalue引用,那么也是个左值 int& f(),f();++it
- a.m,a->p 当a是个lvalue
通俗的说可以取地址的就是左值
prvalue
- 字面值 42 true or null
- 如果函数调用,返回的none-reference,str1+str2,it++,a+b
- &a
- cast expression 到none -reference类型 static_cast<double>(x)
- this 指针
xvalue
rvalue:prvalue + xvalue
rvalue reference: reference to rvalue. 比如&&
xvalue
- 函数返回的是rvalue reference ,int&& f(); f()表达式
- static_cast<char&&>(x)
- a[n],a.m 当a是rvalue
不能对rvalue取地址 &i++[3],&23
不能放在等号右边 static_cast<char&&>(x) = 'a'
用法介绍
- 可以使用rvalue绑定到cosnt lvalue reference上 const int&i=1;
- 可以绑定到右值引用 int&& i = 5;(新标准)
函数重载时,如果参数有const lvalue reference 和 rvalue重载,如果参数为rvalue那么会调用rvalue的重载
void f(int &x){
std::cout<<"lvalue reference overload"<<std::endl;
}
void f(const int &x){
std::cout<<"lvalue reference to const overload"<<std::endl;
}
void f(int &&x){
std::cout<<"rvalue reference overload"<<std::endl;
}
int i=1;
const int ci = 2;
f(i); //lvalue ref
f(ci); //lvalue ref to const
f(3); //rvalue
f(static_cast<int&&>(i)); //rvalue
int&& x = 1;
//这里需要注意 表达式int&& x是个rvalue 但是x变量是个lvalue
//本质变量是个左值
f(x); //lvalue
如果使用xvalue是构造一个对象,那么这个函数的move 语义版本的函数会被调用
- T a = static_cast<T&&>(b) 或者T a(static_cast<T&&>(b)) 移动构造/赋值函数
- f(static_cast<T&&>(a)); f是void f(T)
继续回到之前谈的怎么指示编译器调用move语义的函数
std::vector<MemoryBlock> vec;
MemoryBlock mb1(10);
vec.push_back(mb1); //copy ctor lvalue
//只有xvalue才会调用到move语义的函数,prvalue不会调用到move语义函数
vec.push_back(static_cast<MemoryBlock&&>(mb1));
std::move 本质就是static_cast<MemoryBlock&&> 转换成rvalue
4.完美转发
4.1 forwarding problem
void g(const U&); //复制语义
void g(U&&); //移动语义
void wrapper(U&t){
g(t);
}
void wrapper(U&&t){
//这里虽然U&&t是xvalue 但是 t是个变量 所以t是lvalue
//所以这里要强制转换下
g(static_cast<U&&>(t));
}
4.1 forwarding reference
转发引用是个特殊的引用
- 可以绑定到lvalue
- 可以绑定到rvalue
- 函数参数为模块T
- &&
- 没有cv描述符
//例子
template<class T>
int g(T&& x);
//例子2
template<class T,class U>
int g(const T&&x,U&& u);
const T&&x 不是个转发引用因为有c描述符,只能绑定到rvalue
U&& u 是个转发引用
//例子3
void wrapper(U&&t){
这里怎么使用呢?
g(?t);
}
可以使用新的标准库的g(std::forward<T>(t))
不论参数传入lvalue,rvalue都会保留,所以才叫“完美转发”
4.3 std::forward
怎么实现这个模板
- 模板参数推倒规则Deduction
- 引用折叠
template<class T>
int f(T&& x){}
f(arg)
当arg是lvalue,x被推倒成引用类型(T&)
//这里被推倒成引用类型
int i;
f(i); ==> f<int&>(int&)
f(0); ==> f<int>(int&&)
4.3 引用折叠
引用的应用在模板的类型推倒或者typedef推倒中
- && + && -> &&
- 其他,-> &
//typedef 类型推倒
typedef int& lref;
typedef int&& rref;
int n;
lref& r1 = n;//lref& --> int& & -> int&
lref&& r2 = n;//lref&& --> int& && -> int&
rref& r3 = n;//rref& --> int&& & -> int&
rref& r4 = n;//rref&& --> int&& && -> int&&
模板类型推倒
template<class T>
void wrapper<T&& t>{
g(std::forward<T>(t));
}
template<class T>
T&& forward(std::remove_reference_t<T>& v){
return static_cast<T&&>(v);
}
std::remove_reference_t<T>& 这个要保证你传入进来的是个lvalue
static_cast<T&&> 强制转换rvalue-ref 类型
//例子
wrapper(arg);
如果arg是int类型
1. wrapper<T&& t> -> T被推倒成int&
2. T是int&,T&& -> int& && -> int&
所以如果传入的lvalue 那么结果得到的就是lvalue
同理rvalue
总结:
- forwarding ref
- std::forward 实现
统一的初始化语法
5.1 初始化
- 值初始化 std::string s();
- 直接初始化 std::string s("hello")
- 复制初始化 std::string s = "hello"
- 列表初始化 std::string s{'a','b','c'}
- aggregate 初始化 char a[3] = {'a','b','c'}
- 引用初始化 char& c = a[0]
- 默认初始化 std::string s;
string a[] = {"foo","bar"}
f({"foo","bar"}) //error
vector<string> v = {"foo","bar"} //error
int a(1);//初始化
int b();//函数申明
int c(foo);//歧义
98标准
string a = {"foo","bar"};
void f(string a[]);
f({"foo","bar"});//error
vector<string> v = {"foo","bar"} //error
int a(1);//初始化
int b();//函数申明
int c(foo);//歧义
c++17标准
string a = {"foo","bar"};
void f(string a[]);
f({"foo","bar"});//right
vector<string> v = {"foo","bar"} //right
int a{1};//初始化
int b{1};//初始化
int c{1};//初始化
新标准中,任意的原来初始化小括弧的地方,都可以用大括弧来表示
struct S{
//构造函数1
S(std::initializer_list<int> l);
//构造函数2
s(int t);
}
S s = {1,2,3,4,5}
S s{1}; //这个会调用到构造函数1
//如果想调用到版本2
S s(1);
//这个应该是统一初始化列表函数唯一的需要注意的地方
//std::initializer_list<int>内部是 array of const T
5.2 统一初始化
- {} 如果信息丢失 不接受隐式转换
- int i = {1.5};//errror
- char c = {12}; //ok
- {}不是表达式 所以没有类型,模板中不能当类型推倒
6 class future
6.1 delete,default
class X{
X& operator=(const X&) = delete;//禁用复制
X(const X&) = delete; //禁用拷贝构造
}
class X{
X& operator=(const X&) = default;//使用编译器生成的默认版本函数
X(const X&) = default;
}
6.2 override,final
override 派生类中显式申明要覆盖基类的函数
final 基类中显式申明 不能被覆盖
struct B {
virtual void f() const final;
//final修饰函数后 表示不能被派生类覆盖这个函数
//const修饰后 标识成员函数不会修改成员变量
}
6.3 构造函数delegating
class Foo{
public:
Foo(char x,int y){}
//构造函数代理,只能有代理函数 不能继续初始化其他成员变量
Foo(int y): Foo('a',y){}
Foo(int y): Foo('a',y),other_int(9){}类似这样的 是错的
}
6.4 默认成员初始化
int x = 0;
struct Foo{
inline static s = 1;
int n = ++x;
Foo(){} //默认成员初始化
Foo(int arg): n(arg){}//成员初始化
}
Foo f{10};
std::cout<<x<<std::endl; //0
Foo f1{};
std::cout<<x<<std::endl;//1
6.5 继承构造函数
struct B{
void f(double x){}
};
struct D : public B{
//导入所有的 B::f()s 到当前命名空间
using B::f;
//添加新的f版本函数
void f(int x){}
};
7 总结
- 类型推倒
- 移动语义
- 完美转发
- 统一初始化列表
- class的新特性
网友评论