《深入应用c++11》第二章

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表。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值