Skip to content
0

C++新特性

右值引用

  • 左值和右值的区别在于能否取地址。左值能通过&获取地址,而右值不能。
  • 左值可以出现在=的左边或右边,但右值只能出现在=的右边。
  • 左值包括变量、数组元素、结构体成员等等。右值包括字面常量、诸如x+y等表达式、返回值不是引用的函数等。

右值引用可关联到右值,即可出现在赋值表达式右边但不能对其应用地址运算符的值。

c++
int a = 10; // a是左值,10是右值。
int b = a;
int&& rr1 = 100;  // rr1是右值引用
int&& rr2 = a + b;  // rr2是右值引用

引入右值引用的主要目的之一是实现移动语义。

移动语义

Lambda

表达形式:[]() mutable -> returntype {};

语法描述
[]捕获列表:捕获外部变量供函数体使用。不可省略
[=]值传递方式捕获函数体内使用的外部变量;
[&]引用传递方式捕获函数体内使用的外部变量。
()参数列表:lambda函数的参数。
mutable修饰符:允许在函数体中修改捕获的变量。
因为引用传递方式就可修改外部变量,所以基本不用。
-> returntype返回类型:lambda函数的返回类型。
{}函数体:lambda的函数体。 不可省略
  • 如果使用了 mutable-> returntype,则不能省略(),即使没有参数。
  • 按值传递捕获的变量不受外部修改的影响,函数体内还是原值;按引用传递捕获的变量外部修改会影响函数体内的变量的值。
  • 虽然返回值类型编译器可以推导,但最好还是注明返回值类型。
c++
auto l2 = [] {};  // 最简形式

auto add2 = [](int x, int y) -> int {return x + y;};
cout << add2(1, 2) << endl;

auto a(10), b(20);
auto add3 = [=, &b](int z) -> int {return a + b + z;};
a = 20;
b = 5;
cout << add3(10) << endl;  // 25

智能指针

智能指针类型描述
auto_ptrC++98定义的智能指针模板,C++11后被淘汰了。
unique_ptr独占所有权的智能指针,同一内存只能有一个 unique_ptr 指针。
shared_ptr共享所有权的智能指针,多个 shared_ptr 可以指向同一内存。
采用引用计数,当复制或拷贝时,计数+1;当析构时计数-1,计数为0则释放内存。
weak_ptr弱引用智能指针,用于与 shared_ptr 配合使用,避免循环引用导致的内存泄漏。

初始化

注:xxx_ptr 表示 unique_ptrshared_ptr

c++
// 初始化一个可以指向T类型的空的智能指针
xxx_ptr<T> ptr1;

// 初始化一个指向T类型实例的智能指针
xxx_ptr<T> ptr2(new T());

// 初始化一个可以指向T类型数组的空的智能指针
xxx_ptr<T[]> ptr3; 

// 初始化一个指向T类型数组实例的智能指针
xxx_ptr<T[]> ptr4(new T[]);

// 初始化一个可以指向T类型实例的智能指针,并使用自定义删除器来释放内存
unique_ptr<T, D> ptr5;

// 虽然编译器不会报错,但ptr6无法通过赋值或reset接管对象(运行时异常退出)。
// 要么直接用ptr8的方式,要么就用ptr1的方式并使用ptr1.reset(new T(), D());
shared_ptr<T> ptr6(nullptr, D()); 

// 初始化一个指向T类型实例的智能指针,并使用自定义删除器来释放内存
unique_ptr<T, D> ptr7(new T());
shared_ptr<T> ptr8(new T(), D());

// make_unique(或 make_shared)会同时分配控制块(用于引用计数)和对象本身,
// 相比于 new 方式初始化,少一次内存分配次数,更加简洁高效(推荐)
unique_ptr<T> ptr9 = make_unique<T>(args);
shared_ptr<T> ptr10 = make_shared<T>(args);

unique_ptr

API描述
get()获取接管对象的指针。
reset(...args)销毁当前对象(如果存在),并接管新的对象(如果提供)或重置为空指针。
release()仅释放接管对象的控制权,不会销毁对象。
  • 不支持赋值和复制操作,需要使用std::move()进行移动操作。

shared_ptr

API描述
get()获取接管对象的指针。
reset(...args)当前对象引用计数-1(如果存在),并接管新的对象(如果提供,新对象引用计数+1)或重置为空指针。可指定自定义deleter
use_count()获取当前接管对象的引用计数。空指针调用返回为0
swap()交换接管的对象,原对象的引用计数不变

weak_ptr

std::weak_ptr不能直接构造,必须从一个shared_ptr或另一个weak_ptr构造。它的构造和析构不会引起引用计数的增加或减少。

API描述
lock()返回接管对象的shared_ptr(引用计数+1),如果接管对象已被释放,则返回空的shared_ptr
reset()重置为空指针,注意引用计数不变
use_count()获取当前接管对象的shared_ptr指针的引用计数。
swap()交换接管的对象,原对象的引用计数不变
expired()判断当前weak_ptr是否还有托管的对象,有则返回false,无则返回true
  • 不支持 *-> 对指针的访问

Example:

c++
#include <iostream>
#include <memory>

using namespace std;

class Person
{
private:
  std::string _name;
public:
  Person() { 
    std::cout << "调用Person的构造函数" << std::endl;
    _name = "Unknown";
  }
  Person(string name) {
    std::cout << "调用Person的带参构造函数: " << name << std::endl; 
    _name = name;
  }
  ~Person() { std::cout << "调用Person的析构函数" << std::endl; }
  void doSomething() const { std::cout << _name << " doSomething...\n"; }
};

class PersonDeleter
{
public:
  void operator()(Person* pt) {
    pt->doSomething();
    delete pt;
  }
};

class Girl;

class Boy
{
private:
    weak_ptr<Girl> _girlFriend;
public:
    Boy() 
    {
        cout << "Boy构造函数\n";
    }
    virtual ~Boy()
    {
        cout << "~Boy析构函数\n";
    }
    void setGirlFriend(shared_ptr<Girl> girlFriend)
    {
        cout << "In setGirlFriend a: " << girlFriend.use_count() << endl; // 2
        // 赋值给weak_ptr不会让引用计数+1
        // 这里是因为girlFriend作为值传递方式传参而使计数+1
        // 当函数执行完之后,girlFriend会销毁使计数-1
        _girlFriend = girlFriend;
        cout << "In setGirlFriend b: " << _girlFriend.use_count() << endl; // 2
        // 获取共享指针
        shared_ptr<Girl> spGril =_girlFriend.lock();
        cout << "In setGirlFriend c: " << spGril.use_count() << endl; // 3
        spGril = nullptr;
        cout << "In setGirlFriend d: " << _girlFriend.use_count() << endl; // 2
    }
    void resetGirlFriend()
    {
        if (!_girlFriend.expired())
        {
            _girlFriend.reset();
            cout << "分手成功!\n";
        }
        else
            cout << "已经没有女朋友了!\n";
    }
};

class Girl {
private:
    shared_ptr<Boy> _boyFriend;
public:
    Girl() {
        cout << "Girl构造函数" << endl;
    }

    virtual ~Girl() {
        cout << "~Girl析构函数" << endl;
    }

    void setBoyFriend(shared_ptr<Boy> boyFriend) {
        _boyFriend = boyFriend;
    }
};
c++
#include "smart_ptr.hpp"

void test_unique_ptr()
{
  std::cout << "===================Test unique_ptr==========================\n";
  std::unique_ptr<string> ptr_string(new string("zhangsan"));
  std::unique_ptr<int> ptr_int(new int(100));
  {
    Person* person = new Person();
    std::unique_ptr<Person> p2;
    p2.reset(person);  // 接管person实例的内存空间
    std::unique_ptr<Person> p3;
    p3 = std::move(p2);  // 使用move把左值变为右值后就可以赋值了,p2被置为空
    p3.reset();  // 等同于 p3 = nullptr;
    // auto p = p3.release(); // 仅释放托管对象的控制权,不销毁对象。返回对象指针
    // delete p; // 如果使用release(),则需要手动释放内存
  }
  std::cout << "\n*****测试自定义的 deleter ***** \n";
  {
    Person* person2 = new Person("Tom");
    std::unique_ptr<Person, PersonDeleter> p4(person2);
  }
}
c++
#include "smart_ptr.hpp"

void test_shared_ptr()
{
  std::cout << "===================Test shared_ptr==========================\n";
  std::shared_ptr<int> ptr_int = make_shared<int>(100);
  {
    std::shared_ptr<Person> p1 = make_shared<Person>("Tom");
    std::shared_ptr<Person> p2;
    std::cout << "p2.use_count() = " << p2.use_count() << std::endl;
    p2 = p1;
    std::cout << "count: " << p1.use_count() << " " << p2.use_count() << std::endl;
    // 2 2
    p2.reset();  // 当前接管对象的引用计数-1,并将p2置为空指针。等同于 p2 = nullptr;
    std::cout << "count: " << p1.use_count() << " " << p2.use_count() << std::endl;
    // 1 0
  }
  {
    Person* person = new Person("Lucy");
    std::shared_ptr<Person> p3(person, PersonDeleter());
    std::shared_ptr<Person> p4(p3); // 等同于 p4 = p3;
    std::cout << "count: " << p3.use_count() << " " << p4.use_count() << std::endl;
    p4 = nullptr; // p4没有使用自定义deleter
    std::cout << "count: " << p3.use_count() << " " << p4.use_count() << std::endl;
  }
  {
    Person* person = new Person("Lucy2");
    // 用下面的方式初始化p5,在运行p5.reset时会异常退出
    //std::shared_ptr<Person> p5(nullptr, PersonDeleter());
    std::shared_ptr<Person> p5;
    p5.reset(person, PersonDeleter());
    std::cout << "p5 count: " << p5.use_count() << std::endl;
    p5.reset();  // 使用自定义deleter
    std::cout << "p5 count: " << p5.use_count() << std::endl;
  }
}
c++
#include "smart_ptr.hpp"

void test_weak_ptr()
{
  cout << "===================Test weak_ptr==========================\n";
  shared_ptr<Boy> spBoy(new Boy());
  shared_ptr<Girl> spGirl(new Girl());
  
  spGirl->setBoyFriend(spBoy);

  cout << "Before setGirlFriend: " << spGirl.use_count() << endl; // 1
  spBoy->setGirlFriend(spGirl);
  cout << "After setGirlFriend: " << spGirl.use_count() << endl; // 1
  spBoy->resetGirlFriend();
  cout << spGirl.use_count() << endl;  // 1
}

使用指南

  1. 如果程序需要使用多个指向同一个对象的指针,应该使用shared_ptr;如果有循环引用的情况,则使用weak_ptr
  2. unique_ptr可以转化为shared_ptr,但反之则不行。
  3. 不要把原生对象的指针托管给多个智能指针,这非常危险!
c++
int* x = new int(10);
shared_ptr<int> p1(x);  // 或使用p1.reset(x)的形式;
shared_ptr<int> p2(x);
p1 = nullptr;  // p1所接管的指针所指向的内存空间会被释放。
// p2 所接管的指针将变成悬挂指针。
  1. 如果使用了release(),记得手动释放返回的指针。
  2. 不要delete智能指针get()返回的指针。既然用上了智能指针,那就让代码“智能”一点。
  3. 智能指针作为函数参数或返回值的情况
    函数unique_ptrshared_ptr
    返回值所有权转让给接收返回值的unique_ptr复制行为,引用计数+1,但函数执行完后会自动销毁,引用计数-1
    参数:值传递所有权转让给函数的局部变量参数,函数执行完后会销毁(谨慎)同上
    参数:引用传递所有权仍归函数外的实参,函数执行完后不会销毁计数不变
c++
static unique_ptr<Person> func1(string name)
{
    return unique_ptr<Person>(new Person(name));
}

static void func2(unique_ptr<Person> up)
{
    up->doSomething();
}

static void func3(unique_ptr<Person>& up)
{
    up->doSomething();
}

void test_func_unique_ptr()
{
  auto p5 = func1("P5");
  func3(p5);

  Person* person3 = new Person("P6");
  unique_ptr<Person> p6(person3);
  func2(std::move(p6)); // 函数执行完后,person3的内存会被释放
  cout << "After func2\n";
}

运行结果如下: