unique_ptr
使用智能指针最先想到和优先考虑的应该是独占式智能指针unique_ptr
。
独占式(专属所有权):同一时刻,只能有一个unique_ptr
指向该对象(或者说这块内存)。当unique_ptr
被销毁的时间,它所指的对象也直接被销毁。
语法格式:
std::unique_ptr<指向的对象类型> 智能指针变量名;
初始化
在讨论初始化之前我们先使用原始指针来演示一下内存泄漏的情况
#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;
}
$ ./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)
$
$ ./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指针直接交给智能指针来管理,示例代码如下。
#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;
}
$ 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配合初始化
std::unique_ptr<int> p1; //定义一个可以指向int类型的空智能指针p1
std::unique_ptr<int> p2(new int(100)); //智能指针p2指向一个int类型的对象,对象的值为100
同样是上面的示例程序,我们可以使用new配合智能指针初始化来管理,示例代码如下。
#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;
}
$ 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
基本一致;当使用自定义删除器的时间不能使用,其他情况推荐使用。
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()
初始化来管理,示例代码如下。
#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;
}
$ 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)
$
不支持的初始化方式
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;
移动语义
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
#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
的删除器相对来说复杂一点,需要首先在类型模板参数中传递进去类型名,然后再在参数中指定具体的删除器函数名。
#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表达式形式的删除器,尺寸不会变化;
- 如果是定义了一个函数作为删除器,尺寸会发生变化。
增加尺寸会影响性能,所以自定义删除器要慎用。