Skip to content

unique_ptr

使用智能指针最先想到和优先考虑的应该是独占式智能指针unique_ptr

独占式(专属所有权):同一时刻,只能有一个unique_ptr指向该对象(或者说这块内存)。当unique_ptr被销毁的时间,它所指的对象也直接被销毁。

语法格式:

cpp
std::unique_ptr<指向的对象类型> 智能指针变量名;

初始化

在讨论初始化之前我们先使用原始指针来演示一下内存泄漏的情况

cpp
#include <iostream>
using namespace std;

class Person {
private:
    string name;
    int age;

public:
    Person(string name, int age = 18) {
        this->name = name;
        this->age = age;
        cout << "Person::Person()" << " name=" << this->name << endl;
    }
    ~Person() {
        cout << "Person::~Person()" << " name=" << this->name << endl;
    }
};

int main() {
    Person *p1 = new Person("阿强");
    // delete p1;   // 通过对本行的注释演示内存泄露情况

    return 0;
}
bash
$ ./main 
Person::Person() name=阿强
Person::~Person() name=阿强
The main function is about to end.
$ g++ no-smart-pointer.cc -o main -g
$ valgrind --tool=memcheck --leak-check=full ./main
==10946== Memcheck, a memory error detector
==10946== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==10946== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==10946== Command: ./main
==10946== 
Person::Person() name=阿强
Person::~Person() name=阿强
The main function is about to end.
==10946== 
==10946== HEAP SUMMARY:
==10946==     in use at exit: 0 bytes in 0 blocks
==10946==   total heap usage: 3 allocs, 3 frees, 74,792 bytes allocated
==10946== 
==10946== All heap blocks were freed -- no leaks are possible
==10946== 
==10946== For lists of detected and suppressed errors, rerun with: -s
==10946== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$
bash
$ ./main 
Person::Person() name=阿强
The main function is about to end.
$ g++ no-smart-pointer.cc -o main -g
$ valgrind --tool=memcheck --leak-check=full ./main
==10868== Memcheck, a memory error detector
==10868== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==10868== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==10868== Command: ./main
==10868== 
Person::Person() name=阿强
The main function is about to end.
==10868== 
==10868== HEAP SUMMARY:
==10868==     in use at exit: 40 bytes in 1 blocks
==10868==   total heap usage: 3 allocs, 2 frees, 74,792 bytes allocated
==10868== 
==10868== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==10868==    at 0x4846FA3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==10868==    by 0x10A3F2: main (no_smart_pointer.cc:21)
==10868== 
==10868== LEAK SUMMARY:
==10868==    definitely lost: 40 bytes in 1 blocks
==10868==    indirectly lost: 0 bytes in 0 blocks
==10868==      possibly lost: 0 bytes in 0 blocks
==10868==    still reachable: 0 bytes in 0 blocks
==10868==         suppressed: 0 bytes in 0 blocks
==10868== 
==10868== For lists of detected and suppressed errors, rerun with: -s
==10868== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
$

我们有三种方式可以初始化一个 unique_ptr ,下面我们分别介绍这几种初始化的方式:

方式一:创建指针后交给unique_ptr

同样是上面的示例程序,我们可以把申请后的p1指针直接交给智能指针来管理,示例代码如下。

cpp
#include <iostream>
#include <memory>
using namespace std;

class Person {
private:
    string name;
    int age;

public:
    Person(string name, int age = 18) {
        this->name = name;
        this->age = age;
        cout << "Person::Person()" << " name=" << this->name << endl;
    }
    ~Person() {
        cout << "Person::~Person()" << " name=" << this->name << endl;
    }
};

int main() {
    Person *p1 = new Person("阿强");
    unique_ptr<Person> p2(p1);
    // delete p1;   // 危险行为,不要在交给智能指针管理后释放原始指针

    cout << "The main function is about to end." << endl;
    return 0;
}
bash
$ g++ unique_ptr_01.cc -o main -g 
$ ./main 
Person::Person() name=阿强
The main function is about to end.
Person::~Person() name=阿强
$ g++ no-smart-pointer.cc -o main -g
$ valgrind --tool=memcheck --leak-check=full ./main
==11129== Memcheck, a memory error detector
==11129== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==11129== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==11129== Command: ./main
==11129== 
Person::Person() name=阿强
The main function is about to end.
Person::~Person() name=阿强
==11129== 
==11129== HEAP SUMMARY:
==11129==     in use at exit: 0 bytes in 0 blocks
==11129==   total heap usage: 3 allocs, 3 frees, 74,792 bytes allocated
==11129== 
==11129== All heap blocks were freed -- no leaks are possible
==11129== 
==11129== For lists of detected and suppressed errors, rerun with: -s
==11129== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$

注意

你申请出来的内存已经交给智能指针去管理了,千万不要在交给智能指针管理之后把原始指针给delete释放掉,这将会导致程序出错。

方式二:使用new配合初始化

cpp
std::unique_ptr<int> p1;    //定义一个可以指向int类型的空智能指针p1
std::unique_ptr<int> p2(new int(100));  //智能指针p2指向一个int类型的对象,对象的值为100

同样是上面的示例程序,我们可以使用new配合智能指针初始化来管理,示例代码如下。

cpp
#include <iostream>
#include <memory>
using namespace std;

class Person {
private:
    string name;
    int age;

public:
    Person(string name, int age = 18) {
        this->name = name;
        this->age = age;
        cout << "Person::Person()" << " name=" << this->name << endl;
    }
    ~Person() {
        cout << "Person::~Person()" << " name=" << this->name << endl;
    }
};

int main() {
    unique_ptr<Person> p2(new Person("阿强"));

    cout << "The main function is about to end." << endl;
    return 0;
}
bash
$ g++ unique_ptr_02.cc -o main -g
$ ./main 
Person::Person() name=阿强
The main function is about to end.
Person::~Person() name=阿强
$ valgrind --tool=memcheck --leak-check=full ./main
==11381== Memcheck, a memory error detector
==11381== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==11381== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==11381== Command: ./main
==11381== 
Person::Person() name=阿强
The main function is about to end.
Person::~Person() name=阿强
==11381== 
==11381== HEAP SUMMARY:
==11381==     in use at exit: 0 bytes in 0 blocks
==11381==   total heap usage: 3 allocs, 3 frees, 74,792 bytes allocated
==11381== 
==11381== All heap blocks were freed -- no leaks are possible
==11381== 
==11381== For lists of detected and suppressed errors, rerun with: -s
==11381== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$

方式三:使用make_unique()初始化

C++ 11中并没有make_unique() ,特性与make_shared基本一致;当使用自定义删除器的时间不能使用,其他情况推荐使用。

cpp
std::unique_ptr<int> up1 = std::make_unique<int>(50);
auto up2 = std::make_unique<int>(55);
auto up3(new int(100)); // 不能使用auto自动推断的这种形式,不然推断出来的是原始指针而不是智能指针

同样是上面的示例程序,使用make_unique()初始化来管理,示例代码如下。

cpp
#include <iostream>
#include <memory>
using namespace std;

class Person {
private:
    string name;
    int age;

public:
    Person(string name, int age = 18) {
        this->name = name;
        this->age = age;
        cout << "Person::Person()" << " name=" << this->name << endl;
    }
    ~Person() {
        cout << "Person::~Person()" << " name=" << this->name << endl;
    }
};

int main() {
    unique_ptr<Person> p2 = make_unique<Person>("阿强");

    cout << "The main function is about to end." << endl;
    return 0;
}
bash
$ g++ unique_ptr_03.cc -o main -g
$ ./main 
Person::Person() name=阿强
The main function is about to end.
Person::~Person() name=阿强
$ valgrind --tool=memcheck --leak-check=full ./main
==11514== Memcheck, a memory error detector
==11514== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==11514== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==11514== Command: ./main
==11514== 
Person::Person() name=阿强
The main function is about to end.
Person::~Person() name=阿强
==11514== 
==11514== HEAP SUMMARY:
==11514==     in use at exit: 0 bytes in 0 blocks
==11514==   total heap usage: 3 allocs, 3 frees, 74,792 bytes allocated
==11514== 
==11514== All heap blocks were freed -- no leaks are possible
==11514== 
==11514== For lists of detected and suppressed errors, rerun with: -s
==11514== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$

不支持的初始化方式

cpp
std::unique_ptr<int> up1 = std::make_unique<int>(100);
// 不支持使用拷贝构造的初始化方式,下面几种初始化的方式都不支持
std::unique_ptr<int> up2(up1);
std::unique_ptr<int> up3 = up1;
std::unique_ptr<int> up4;
up4 = up1;

移动语义

cpp
std::unique_ptr<int> up1 = std::make_unique<int>(100);

// 支持使用移动构造的初始化方式,下面几种初始化的方式都支持
std::unique_ptr<int> up2(std::move(up1));
std::unique_ptr<int> up3 = std::move(up1);
std::unique_ptr<int> up4;
up4 = std::move(up1);

推荐的初始化方式

比较建议使用make_unique()进行初始化。

常用操作

  • release():放弃对所指对象的控制权(相当于切断与其所指对象之间的联系),将智能指针置空,并返回其所指对象的原始指针;我们可以手动的delete其返回对象,也可以用来初始化其他智能指针或者用来给其他智能指针赋值。
  • reset()
    • 不带参数的情况:释放智能指针所指的对象,并将智能指针置空;
    • 带参数的情况:释放智能指针所指的对象,并让该智能指针指向新对象。
  • =nullptr:释放智能指针所指的对象,并将智能指针置空。
  • 指向一个数组:使用方法和shared_ptr基本一致,如果忘记了可以去回顾一下。
  • get():返回智能指针中保存的原始指针。
  • *解引用:获取该智能指针所指的对象,可以直接进行操作。但是对于定义的内容是数组的类型是没有*解引用运算符的。
  • swap():交换两个智能指针所指向的对象。
  • 名字作为判断条件:直接将智能指针的名字放在if中进行判断。
  • 转换成shared_ptr类型:如果unique_ptr为右值,就可以将它赋值给shared_ptr。因为shared_ptr包含一个显式构造函数,用于将unique_ptr转换为shared_ptr。转换后shared_ptr将接管之前unique_ptr所管理的对象。
    • 转换成shared_ptr的方式可以通过返回函数内的局部对象,
    • 也可以直接将unique_ptr转换为右值

返回unique_ptr

cpp
#include <iostream>

std::unique_ptr<int> func() {
    return std::unique_ptr<int>();
}

int main() {
    std::unique_ptr<int> p1 = func();

    return 0;
}

指定删除器

shared_ptr一样,默认的删除器都是delete,但是shared_ptr相对来说简单一些,在参数里指定删除器的函数名就可以了;unique_ptr的删除器相对来说复杂一点,需要首先在类型模板参数中传递进去类型名,然后再在参数中指定具体的删除器函数名。

cpp
#include <iostream>

void mydlt(std::string *del) {
    delete del;
    del = nullptr;
    std::cout << "自定义的删除器执行了" << std::endl;
}

int main() {
    //方法01
    //typedef void (*dlt)(std::string *);	  //要使用自定义删除器必须先定义一个删除器的类型
    //std::unique_ptr<std::string, dlt> p1(new std::string("Hello CPP"), mydlt);
    
    //方法02
    //using dlt = void (*)(std::string *);    //或者使用using来定义
    //std::unique_ptr<std::string, dlt> p1(new std::string("Hello CPP"), mydlt);
    
    //方法03
    //typedef decltype(mydlt) *dlt;   //或者使用decltype来进行定义。decltype返回的是函数类型,加*表示函数指针类型
    //std::unique_ptr<std::string, dlt> p1(new std::string("Hello CPP"), mydlt);

    //方法04
    std::unique_ptr<std::string, decltype(mydlt) *> p1(new std::string("Hello CPP"), mydlt);
    
    //方法05
    /*
    auto mydlt2 = [](std::string *del){
        delete del;
        del = nullptr;
        std::cout << "自定义的lamada删除器执行了" << std::endl;
    };

    std::unique_ptr<std::string, decltype(mydlt2)> p1(new std::string("Hello CPP"), mydlt2);
    */
    return 0;
}

指定删除器额外说明

就算是两个不同的shared_ptr指定了不同的删除器,只要他们所指向对象的类型相同,那么这两个shared_ptr就也属于同种类型。但是unique_ptr不一样,指定删除器的时间会影响unique_ptr的类型。所以从灵活性上来讲,shared_ptr设计的更灵活。

在讲解shared_ptr的时间,删除器不同但指向的类型一样的shared_ptr可以放在同一个容器中,vector<shared_ptr>unique_ptr如果删除器不同,那么就等于整个unique_ptr类型不同,这种类型不同的unique_ptr是不能放在同一个容器中的。

尺寸问题

通常情况下unique_ptr尺寸和原始指针尺寸一样大;如果添加了自己的删除器,尺寸有可能增加,也有可能不增加,是一种可能性。

  • 如果是lamada表达式形式的删除器,尺寸不会变化;
  • 如果是定义了一个函数作为删除器,尺寸会发生变化。

增加尺寸会影响性能,所以自定义删除器要慎用。

参考链接