c++11新特性

  1. 左值引用和右值引用的区别?右值引用的意义?
    左值引用是对左值的引用,右值引用是对右值的引用。
    功能差异:左值引用是为了避免对象拷贝,如函数传参、函数返回值。右值引用是为了实现移动语义和完美转发。
    怎么区分左值和右值?
    左值可以在等号的左边,可以取地址,具名,比如:变量名、返回左值引用的函数调用、前置自增自减、赋值运算或符号赋值运算、解引用。
    右值只能在等号的右边,不能取地址,不具名,右值有纯右值,比如字面值、返回非引用类型的函数调用、后置自增自减、算术逻辑比较表达式,右值还有将亡值,主要为C++11新引入的与右值引用(移动语义)相关的值类型,可以通过move把左值强制转换成将亡值,将亡值将用来触发移动构造或移动赋值构造,并进行资源转移,最后调用析构函数。
    const的左值引用可以指向右值,但不能修改这个值。右值引用可以通过std::move可以执行左值。声明出来的左值引用和右值引用都是左值。
    移动语义是为了对象赋值时,避免资源的重新分配,比如移动构造和移动拷贝构造,stl的unique_ptr也有用到。
    完美转发指的是,函数模板可以将自己的参数完美的转发给内部调用的其他函数,完美指的是不仅能转发参数的值,还能保证转发时参数的左右值属性保持不变。
    借用万能引用,通过引用的方式来接收左右参数的值。万能引用折叠的原则:参数为左值或者左值引用,T &&将转化为int &(假设传进来的值为int类型),参数为右值或者右值引用,T &&将转化为int &&,std::forward(v),T为左值引用,v将转化为T类型的左值,T为右值引用,v将转化为T类型的右值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    #include <stdio.h>
    #include <string>
    #include <algorithm>

    using namespace std;

    class ResourceOwner
    {
    public:
    ResourceOwner(const char res[])
    {
    theResource = new string(res);
    }

    ResourceOwner(const ResourceOwner &other)
    {
    printf("copy %s\n", other.theResource->c_str());
    theResource = new string(other.theResource->c_str());
    }

    ResourceOwner(ResourceOwner &&other)
    {
    printf("copy2 %s\n", other.theResource->c_str());
    theResource = other.theResource;
    other.theResource = nullptr;
    }

    ResourceOwner &operator=(const ResourceOwner &other)
    {
    ResourceOwner tmp(other);
    swap(theResource, tmp.theResource);
    printf("assign %s\n", other.theResource->c_str());
    }

    ResourceOwner &operator=(ResourceOwner &&other)
    {
    theResource = other.theResource;
    other.theResource = nullptr;
    printf("assign2 %s\n", theResource->c_str());
    }

    ~ResourceOwner()
    {
    if (theResource)
    {
    printf("destructor %s\n", theResource->c_str());
    delete theResource;
    }
    }

    private:
    string *theResource;
    };

    void testCopy()
    {
    // case 1
    printf("=====start testCopy()=====\n");
    ResourceOwner res1("res1");
    ResourceOwner res2 = res1;
    // copy res1
    printf("=====destructors for stack vars, ignore=====\n");
    }

    void testAssign()
    {
    // case 2
    printf("=====start testAssign()=====\n");
    ResourceOwner res1("res1");
    ResourceOwner res2("res2");
    res2 = res1; // 先调拷贝再调赋值,竟然还要调用一次拷贝!
    // copy res1, assign res1, destrctor res2
    printf("=====destructors for stack vars, ignore=====\n");
    }

    void testRValue()
    {
    // case 3
    printf("=====start testRValue()=====\n");
    ResourceOwner res2("res2");
    res2 = ResourceOwner("res1"); // 先调拷贝再调赋值,加了移动复制运算后,直接移动就行了!
    // copy res1, assign res1, destructor res2, destructor res1
    printf("=====destructors for stack vars, ignore=====\n");
    }

    ResourceOwner getRO()
    {
    ResourceOwner res1("tmp");
    return res1;
    }

    void testFuncReturn()
    {
    // case 4
    printf("=====start testFuncReturn()=====\n");
    ResourceOwner res2("res2");
    res2 = getRO();
    // copy res1, assign res1, destructor res2, destructor res1
    printf("=====destructors for stack vars, ignore=====\n");
    }

    int main()
    {
    testCopy();
    testAssign();
    testRValue();
    testFuncReturn();
    }

    没加移动语义前:

    加了移动语义后:

    返回值不需要std::move,编译器会进行返回值优化。

  2. C++11智能指针以及使用场景?
    指针管理的困境:资源释放了,指针没有置空,比如野指针(资源释放了,但没有置为nullptr,还在继续使用)、指针悬挂(多个指针指向同一个资源,其中一个指针释放了资源也置空了,但是其他的指针并不知道)、踩内存(拿着野指针去修改新分配的资源);没有释放资源,产生内存泄漏;重复释放资源,引发coredump;
    通过RAII的方式来解决,有shared_ptr、weak_ptr和unique_ptr。
    unique_ptr 类似于一个独占所有权的指针,只能有一个指针管理一个对象,当 unique_ptr 被销毁时,它所管理的对象也会被随之销毁。它的定义和用法如下:
    1
    2
    std::unique_ptr<int> ptr(new int(10));
    std::cout << *ptr << std::endl;

shared_ptr 是一种共享所有权的指针,多个指针可以同时管理一个对象,当最后一个 shared_ptr 被销毁时,它所管理的对象才会被随之销毁。它的定义和用法如下:

1
2
3
std::shared_ptr<int> ptr1(new int(10));
std::shared_ptr<int> ptr2 = ptr1;
std::cout << *ptr1 << " " << *ptr2 << std::endl;

weak_ptr 是 shared_ptr 的一种扩展,它可以不增加所管理对象的引用计数而与 shared_ptr 交互,但它不能直接访问所管理的对象,需要通过 lock() 函数获得一个指向所管理的对象的 shared_ptr。通常用于解决 shared_ptr 循环引用的问题。它的定义和用法如下:

1
2
3
4
5
6
std::shared_ptr<int> ptr(new int(10));
std::weak_ptr<int> wp = ptr;
if (!wp.expired()) {
std::shared_ptr<int> ptr2 = wp.lock();
std::cout << *ptr2 << std::endl;
}

  1. lambda表达式,如下,x相当于默认是加了const的,如果要修改,需要加mutable
    1
    2
    3
    4
    auto f = [x] () mutable {
    x = 2; // 如果不加mutable,报错:'x' cannot be modified as it is being captured by value
    std::cout << x << std::endl; // 输出 2
    };

constexpr 是在 C++11 标准中引入的一种修饰符,用于声明一个在编译时就能确定其值的表达式或函数,其运行速度可以相当于直接写出该值。由于在编译时就能确定值,因此 constexpr 可使编译器为编译期计算并优化表达式。

1
constexpr int k = 5 * 7;

简述shared_ptr

  1. shared_ptr 实现共享式拥有概念,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字 share 就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。
  2. 可以通过成员函数 usecount() 来查看资源的所有者个数,除了可以通过 new 来构造,还可以通过传入autoptr, uniqueptr,weakptr 来构造。当我们调用 release() 时,当前指针会释放资源所有权,计数减一。当计数等于 0 时,资源会被释放。
  3. sharedptr 是为了解决 autoptr 在对象所有权上的局限性 (auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针。
nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!