1,区分c++中的左值与右值
左值是表达式结束后依然存在的持久对象,右值是表达式结束后不再存在的临时对象。
最简单的区分方法是:能对表达式取地址的是左值,不能的是右值
右值=将亡值+纯右值
2,常量的左值引用是一个“万能”引用类型,可以接受左值、右值、常量左值和常量右值。
3,只有当发生自动类型推导的时候(例如函数模板的类型自动推导,或auto关键字),&&才是一个通用的引用universal references。
template
void f(constr T&&& param);
这个param其实也是个右值引用,通用引用仅仅在T&&下发生,任何一点附加条件都会使之失效,而变成一个普通的右值引用。
注意区分:
一个通用引用必须被初始化,它是左值还是右值取决于它的初始化,如果被一个左值初始化则是一个左值;反之则是一个右值。这个其实就是引用折叠。
T&&这种未定的引用类型,作为参数时可能被一个左值引用或者右值引用的参数初始化,则其自身的类型会发生变化。
1)所有的右值引用叠加到右值引用上,仍然是一个右值引用
2)所有的其他引用类型之间的叠加都将变为一个左值引用。
另外,T&&作为参数的时候,需要注意T的类型
i)当T&&作为模板参数时,如果被左值X初始化,则T的类型为X&,如果被右值X初始化,则T的类型为X
4,编译器会将已命名的右值引用视为左值,而将未命名的右值视为右值。
void PrintValue(int& i)
{
std::cout<<"lvalue:"<<i<<std::endl;
}
void PrintValue(int&& i)
{
std::cout<<"rvalue:"<<i<<std::endl;
}
void Forward(int&& i)
{
PrintValue(i);
}
int main(int argc, char **argv) {
int i=0;
PrintValue(i);
PrintValue(1);
Forward(2);
}
结果:
lvalue:0
rvalue:1
lvalue:2
Forward转发的是一个右值,但是在PrintValue的时候又变成了左值。
5,关于c++11新增的强类型枚举
/**
*“传统”的C++枚举类型有一些缺点:
* 它会在一个代码区间中抛出枚举类型成员(如果在相同的代码域中的两个枚举类型具有相同名字的枚举成员,这会导致命名冲突),
* 它们会被隐式转换为整型,并且不可以指定枚举的底层数据类型。
*/
enum Status1{Ok, Error};
//enum Status2{Ok, Error}; // Err, 导致命名冲突, Status1已经有成员叫Ok, Error
// 在C++11中,强类型枚举解决了这些问题
enum class StatusN1 {Ok, Error};
enum struct StatusN2 {Ok, Error};
//StatusN1 flagN1 = Ok; // err, 必须使用强类型名称
StatusN2 flagN2 = StatusN2::Ok;
enum class C : char {C1 = 1, C2 = 2}; // 指定枚举的底层数据类型
enum class D : unsigned int { D1 = 1, D2 = 2, Dbig = 0xFFFFFFF0U };
std::cout << sizeof(C::C1) << std::endl; // 1
std::cout << sizeof(D::D1) << std::endl; // 4
std::cout << sizeof(D::Dbig) << std::endl; // 4
6,对于含有堆内存的类,需要提高深拷贝的拷贝构造函数
7,一般在提供移动构造函数的同时也会提供一个拷贝构造函数,以防止移动不成功的时候还能拷贝,提高代码安全性。
8,std::move方法将左值转为右值,从而方便移动语义。move是将所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。
将左值变成右值引用,然后应用move语义调用构造函数,避免了拷贝。
move对于拥有形如对内存、文件句柄等资源的成员的对象有效。如果是一些基本类型,比如int或者char[10]数组等,如果应用move,仍然发生拷贝,因为没有对应的移动构造函数。
class A
{
public:
A():m_ptr(new int(0))
{
std::cout<<"construct"<<std::endl;
}
A(const A& a):m_ptr(new int(*a.m_ptr))
{
std::cout<<"copy construct"<<std::endl;
}
A(A&& a):m_ptr(a.m_ptr)
{
a.m_ptr = nullptr;
std::cout<<"move construct"<<std::endl;
}
~A()
{
std::cout<<"destruct"<<std::endl;
delete m_ptr;
}
private:
int* m_ptr;
};
A get(int flag)
{
A a;
A b;
if(flag)
return a;
else
return b;
}
int main(int argc, char **argv) {
static const int& flag=0;
A a=get(flag);
}
结果:
construct
construct
move construct
destruct
destruct
destruct
可以看到没有调用拷贝构造函数,调用的是移动构造函数。
9,forward与完美转发
记住右值引用a是一个绑定了右值对象的左值
一个右值引用类型作为函数的形参,在函数内部再转发该参数的时候它已经变为了一个左值,丢失原来的类型。
完美转发:在函数模板中,完全依靠模板的参数的类型(保持参数的左右值属性),将参数传递给函数模板中调用的另外一个函数。
void PrintT(int& t)
{
cout<<"lvalue"<<endl;
}
template<typename T>
void PrintT(T && t)
{
cout<<"rvalue"<<endl;
}
template<typename T>
void TestForward(T && v)
{
PrintT(v);
PrintT(std::forward<T>(v));
PrintT(std::move(v));
}
int main(int argc, char **argv) {
TestForward(1);
int x=1;
TestForward(x);
TestForward(std::forward<int>(x));
return 0;
}
结果:
lvalue
rvalue
rvalue
lvalue
lvalue
rvalue
lvalue
rvalue
rvalue
10, emplace_back减少内存拷贝和移动
emplace_back能就地通过参数构造对象,不需要拷贝和移动内存,通常优先使用empalce_back来代替push_back。
但是emplace_bake要求对象有对应的构造函数,如果没有则会报错。
struct A
{
public:
int x;
double y;
// /A(int a,double b):x(a),y(b){}
};
int main(int argc, char **argv) {
vector<A> v;
v.emplace_back(1,2);
cout<<v.size()<<endl;
return 0;
}
去掉显式的构造函数,会报error,但是能运行正常并打出vector的长度为1,还是建议在有显式构造函数的时候用emplace_back往容器添加元素。
11,有序容器内部是红黑树,会在元素插入的时候自动进行排序,无序容器的内部是hash表。