常用C++11特性

详细介绍:C++11/14 高速上手教程

一、右值引用与std::move

如下代码:

std::string tmp("bert")
std::string name(tmp)

第一行声明了一个对象tmp,用"bert"字符串进行初始化

第二行声明了一个对象name,使用参数tmp调用string的构造函数,因此对象name复制了"bert"的一个副本,如下:

在这里插入图片描述

如下代码:

std::string tmp(“bert”);
std::string name(std::move(tmp));

直接将tmp中的内容移动到name中,而不是复制一份,用图表示为:

在这里插入图片描述

二、智能指针

  • unique_ptr

    #include <string.h>
    #include <stdio.h>
    #include <memory>
    #include <iostream>
    
    using namespace std;
    
    class Student {
    public:
        Student() {
            cout << "Construct student" << endl;
        }
    
        ~Student() {
            cout << "Destruct student" << endl;
        }
    
        bool Register() {
            return true;
        }
    
        bool Enroll() {
            return true;
        }
    };
    
    
    int main() {
        Student* p = new Student();
        if (!p->Register()) {
            delete p;
            return -1;
        }
    
        if (!p->Enroll()) {
            delete p;
            return -2;
        }
    
        delete p;
        return 0;
    }
    
    

    该代码使用 new 在堆上分配一个 Student 对象,并对其调用了两个成员函数:Register 和 Enroll。任意一个失败都将返回失败,并且不能忘记 delete 资源。在三处 return 语句之前,必须加上 delete p;否则就可能造成资源泄漏;如果使用 unique_ptr,就可以在指针 p 退出作用域时,自动释放所分配的资源,完整代码如下:

    #include <string.h>
    #include <stdio.h>
    #include <memory>
    #include <iostream>
    
    using namespace std;
    
    class Student {
    public:
        Student() {
            cout << "Construct student" << endl;
        }
    
        ~Student() {
            cout << "Destruct student" << endl;
        }
    
        bool Register(Student* p) {
            return true;
        }
    
        bool Enroll(Student* p) {
            return true;
        }
    };
    
    
    // 编译 g++ -std=c++11 -o unique_ptr_test unique_ptr_test.cc
    int main() {
        unique_ptr<Student> p(new Student());
        if (!p->Register()) {
            // 不需要任何操作,p指向的资源会自动释放
            return -1;
        }
    
        if (!p->Enroll()) {
            // 不需要任何操作,p指向的资源会自动释放
            return -2;
        }
    
        // 不需要任何操作,p指向的资源会自动释放
        return 0;
    }
    

    利用 unique_ptr,无须操心释放资源,避免内存泄漏。

    另外,unique_ptr 也适用于消除“内部分配,外部释放”这种易错的机制,比如 c 语言的 strdup 函数:

    int main() {
            //  strdup函数内部为字符串分配内存,并返回其地址
            char* p = strdup("bert");
    
            // ...使用p
    
            // 使用者必须释放strdup分配的内存,否则内存泄漏
            free(p);
            return 0;
        }
    

    可以看到 strdup 函数的使用,给使用者打开了犯错的窗户,一不小心就会内存泄漏。而使用 unique_ptr 则可以解决这一问题:

    std::unique_ptr<char []> my_strdup(const char* s) {
            if (s == nullptr)
                return std::unique_ptr<char []>(nullptr);
    
            //计算字符串长度
            size_t len = strlen(s);
    
            //用智能指针管理分配的内存
            std::unique_ptr<char []> str(new char[len+1]);
    
            //拷贝字符串s
            strcpy(str.get(), s);
    
            //返回分配的新字符串
            return str;
        }
        int main() {
            auto p = my_strdup("bert");
    
            // ...
            // 使用p
    
            // 使用者什么都不需要做,无须担心内存泄漏
            return 0;
        }
    

    unique_ptr 是旧标准库中 auto_ptr 的升级版,由于独占性语义,它不允许被复制,但是可以 move:

    std::unique_ptr p(new Student());
    std::unique_ptr p2(std::move(p));
    

    所以,unique_ptr 是可以被放到 STL 容器中;旧版本要求容器中的元素必须可以被复制,而现在放宽了:movable 的对象也是可以放入容器的:

    std::vector<std::unique_ptr<int>> ptr_vec;
    ptr_vec.push_back(std::unique_ptr<int>(new int(123)));
    
  • shared_ptr

    shared-ptr 是功能非常强大的智能指针,和 unique_ptr 不同的是,它是基于引用计数的,可以被多个所有者共同持有;当最后一个持有者退出作用域时,资源自动释放;它完美解决了资源释放时机的问题,试想一下多个裸指针指向同一个资源时,释放资源将是多么头疼和难以正确,甚至需要使用观察者模式来帮助使用者清理指针。而shared-ptr 完美解决了这一问题,完整代码如下:

    #include <string.h>
    #include <stdio.h>
    #include <memory>
    #include <thread>
    #include <iostream>
    
    using namespace std;
    
    typedef int Resourse;
    
    shared_ptr<Resourse> CreateResourse() {
        return make_shared<Resourse>(1);
    }
    
    void User1(shared_ptr<Resourse> p) {
        if (p) {
            //1 使用p
            cout << "use p in user1 thread." << endl;
        }
        return;
    }
    
    void User2(shared_ptr<Resourse> p) {
    
        this_thread::sleep_for(chrono::milliseconds(10));
    
        if (p) {
            //2 使用p
            cout << "use p in user2 thread." << endl;
        }
        return;
    }
    
    int main() {
        shared_ptr<Resourse> res = CreateResourse();
    
        // 启动线程t1和t2,运行user函数,使用res
        thread t1(User1, res);
        thread t2(User2, res);
    
        // 等待线程结束,谁也不需要考虑res资源的释放
        t1.join();
        t2.join();
        return 0;
    }
    

    shared_ptr 类似于 java 中的引用:多个引用指向一个对象,只有最后一个引用失效时候,该对象才可以被垃圾回收。

  • weak_ptr
    weak_ptr 是配合 shared_ptr 存在,主要是为了解决两个问题:一是循环引用问题,使得资源无法释放;
    例如 A 对象含有一个 shared_ptr<B>,而 B 对象也含有一个 shared_ptr<A>,那么很容易产生循环引用,使得内存无法释放。weak_ptr 要解决的另外一个问题是臭名昭著的悬挂指针(dangling pointer):指针指向的内存被删除;一个简单的场景是,A 线程创建资源,并传递给 B 线程,B 线程只读访问资源;但是 A 线程随后可能释放了资源,B 没有感知,而得到了一个悬挂指针。

    #include <string.h>
    #include <stdio.h>
    #include <memory>
    #include <thread>
    #include <iostream>
    
    using namespace std;
    
    typedef int Resourse;
    
    shared_ptr<Resourse> g_resourse;
    
    void thread_a() {
        // 2. 创建全局资源
        g_resourse = make_shared<Resourse>(1);
    
        // 3. 睡眠3秒钟
        this_thread::sleep_for(chrono::seconds(3));
    
        // 6. 释放资源
        g_resourse = nullptr;
        cout << "free resourse, thread A exit." << endl;
    }
    
    void thread_b() {
        //  1. 休眠,让线程A先创建资源
        this_thread::sleep_for(chrono::milliseconds(100));
    
        // 4. 创建weak_ptr访问资源,它可以有效检测出悬挂指针:
        weak_ptr<Resourse> pw(g_resourse);
    
        // 5. 隔一秒钟访问资源,若资源被释放了,则退出线程;
        int i = 0;
        while (1) {
            i++;
    
            // 调用weak_ptr的lock()尝试提升到shared_ptr
            auto res(pw.lock());
    
            if (res) {
                // 在6之前: 提升成功,指针res有效,可以使用资源,然后睡眠1秒钟
                cout << i << ":Success read resourse from thread B." << endl;
                this_thread::sleep_for(chrono::seconds(1));
            } else {
                cout << "Fail read resourse from thread B, exit." << endl;
                return; // 7. 说明资源被释放了,出现了"悬挂指针"情况,线程退出
            }
        }
    }
    
    int main() {
        //启动线程A
        std::thread t_a(thread_a);
        //启动线程B
        std::thread t_b(thread_b);
        // 请注意看线程代码注释中的序号,大致代表了代码的执行顺序
    
        //等待线程结束
        t_a.join();
        t_b.join();
        return 0;
    }
    

    输出:

    1:Success read resourse from thread B.
    2:Success read resourse from thread B.
    3:Success read resourse from thread B.
    free resourse, thread A exit.
    Fail read resourse from thread B, exit.
    

    首先 A 线程释放了资源并退出,然后 B 线程使用 weak_ptr 感知到了资源释放,避免了出现悬挂指针错误

三、lambda,bind,function

C++11引入了lambda,它极大的方便了程序员编写临时函数,甚至可以模拟闭包。而bind可以适配函数签名,及其灵活。

#include <string.h>
#include <stdio.h>
#include <memory>
#include <functional>
#include <iostream>

using namespace std;

int main() {
    int r = 0;

    // lambda,是一个可执行对象,类型是void (int* )
    auto updateLambda = [](int* res) { (*res)++; };
    // 将lambda赋值给callback,后者类型是 void (), 由于签名不符,需要bind做适配
    std::function<void ()> callback = std::bind(updateLambda, &r);

    // 执行callback
    callback();

    // 现在,r应该递增为1
    cout << "From main: r = " << r << endl;
    return 0;
}

输出

From main: r = 1

使用 bind 将 void (int*)类型的 lambda,适配到类型为 void ()的 function

四、语法糖:auto,foreach

以前声明一个迭代器:

map<int, int> mm;
map<int, int>::const_iterator it(mm.begin());

现在只需要:

auto it(mm.begin());

以前遍历 map:

for (map<int, int>::const_iterator it(mm.begin()); it != mm.end(); ++it) {
    cout << it->first << “ -> ” << it->second << endl;
}

现在:

for (const auto& kv : mm) {
    cout << kv.first << “ -> ” << kv.second << endl;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章