C++11 智能指针 unique_ptr
C++11 智能指针 unique_ptr
Written on 2023-01-17
个人学习智能指针记录合集:
std::unique_ptr
称为独享智能指针,它独占某个对象管理的所有权,与shared_ptr
可以同时有多个共享智能指针拥有某个对象管理的所有权不同。
当unique_ptr
销毁或者reset
,就会释放(析构)被管理的对象了。
既然是独占,那么就不能有多个unique_ptr
指向同一个对象,unique_ptr
也就不能支持复制操作。
常用初始化及方法
unique_ptr
的常用初始化基本与shared_ptr
有重合。- 但是不能使用复制构造函数,可以使用移动构造函数
- 但对应的
make_unique
是在 C++14 起提出的
unique_ptr
的常用方法基本与shared_ptr
一致。- 对
unique_ptr
使用.reset()
,其原管理的对象就被析构了 .get()
,取得原始指针- 操作符
*
、->
,解引用指向被管理对象的指针
- 对
下面展示了unique_ptr
的常用初始化和方法,以及其自动管理对象的展现。
#include <iostream>
#include <memory>
using namespace std;
class Person{
public:
Person(){ cout << "Constructor: person's age = " << m_age << endl; }
Person(int age) : m_age(age){ cout << "Constructor: person's age = " << m_age << endl; }
~Person(){ cout << "Destructor: person's age = " << m_age << endl; }
void getAge(){ cout << "Person's age = " << m_age << endl; }
private:
int m_age = 0;
};
int main()
{
// 常用初始化
unique_ptr<Person> uPtr1 {new Person()};
unique_ptr<Person> uPtr2 = make_unique<Person>(18);
unique_ptr<Person> uPtr3 {make_unique<Person>(20)};
// 程序段
{
cout << endl << "Enter block" << endl; // 进入程序段
unique_ptr<Person> uPtr4 {make_unique<Person>(22)};
uPtr4->getAge(); // 解引用
(uPtr4.get())->getAge(); // 获取原始指针
}
cout << "Exit block" << endl << endl; // 退出程序段
uPtr1.reset(); // 释放被uPtr1管理对象的所有权
uPtr2.reset(new Person(24)); // 释放被uPtr2管理对象的所有权,转为管理另一个对象
unique_ptr<Person> uPtr5 = move(uPtr3); // 通过移动构造函数初始化
if(uPtr3 == nullptr)
cout << "uPtr3 is nullptr" << endl;
cout << endl << "Before main return" << endl;
return 0;
}
/** 输出:
Constructor: person's age = 0
Constructor: person's age = 18
Constructor: person's age = 20
Enter block
Constructor: person's age = 22
Person's age = 22
Person's age = 22
Destructor: person's age = 22
Exit block
Destructor: person's age = 0
Constructor: person's age = 24
Destructor: person's age = 18
uPtr3 is nullptr
Before main return
Destructor: person's age = 20
Destructor: person's age = 24
**/
同样的,当unique_ptr
被销毁,其管理的对象就同时析构;
避免抛出异常造成的内存泄漏
能够避免因为某个地方异常导致程序中断,从而没有释放内存造成的内存泄露
// ...
Object* p = new Object;
p->func(); // 如果调用成员方法func(),导致程序异常,程序可能在此处中断了
delete p; // 如果中断了,程序因此并不会运行到此,也就没有释放动态申请的内存p,造成了内存泄漏
// ...
unique_ptr<Object> p {make_unique<Object>()};
p->func(); // 使用unique_ptr管理Object,即使调用成员方法func()抛出异常了
// 资源最后也会释放,没有之前的那个问题
释放被管理对象的所有权,但不析构被管理对象
pointer release() noexcept;
.release()
,会释放被管理对象的所有权,但被管理对象不会析构;
会同时把unique_ptr
设为nullptr
,但要记得手动释放delete
。
unique_ptr<Person> uPtr1 {new Person()};
Person *p = uPtr1.release( );
// ...
delete p;
p = nullptr;
释放被管理对象的所有权,析构被管理对象
unique_ptr<Person> uPtr1 {new Person()};
unique_ptr<Person> uPtr2 {new Person()};
uPtr1 = nullptr; // 会析构被管理对象
uPtr2.reset(); // 会析构被管理对象
通过std::move()
或.release()
初始化
unique_ptr
不支持拷贝构造函数,但支持移动构造函数,或者使用.release()
来初始化
unique_ptr<Person> uPtr1 {new Person()};
unique_ptr<Person> uPtr2 = uPtr1; // error
unique_ptr<Person> uPtr3(uPtr1); // error
unique_ptr<Person> uPtr4(uPtr1.release()); // ok
unique_ptr<Person> uPtr5(move(uPtr4)); // ok
unique_ptr<Person> uPtr6 = move(uPtr5); // ok
指定分配函数和删除器
Person* cusAlloc(int age){
cout << "Allocating person's age = " << age << endl;
return new Person{age};
}
void cusDel(Person *p){
cout << "Deallocating "; p->getAge();
delete p;
}
int main( ){
// 使用`decltype`
unique_ptr<Person, decltype(&cusDel)> uPtr1{cusAlloc(18), cusDel};
// 直接填写指定删除器函数的指针类型
unique_ptr<Person, void(*)(Person *)> uPtr2{cusAlloc(22), cusDel};
// 使用using
using cusDelPtr = void(*)(Person *);
unique_ptr<Person, cusDelPtr> uPtr3{cusAlloc(24), cusDel};
cout << endl << "Before main return" << endl;
return 0;
}
/** 输出:
Allocating person's age = 18
Constructor: person's age = 18
Allocating person's age = 22
Constructor: person's age = 22
Allocating person's age = 24
Constructor: person's age = 24
Before main return
Deallocating Person's age = 24
Destructor: person's age = 24
Deallocating Person's age = 22
Destructor: person's age = 22
Deallocating Person's age = 18
Destructor: person's age = 18
**/
decltype
是一个函数指针,指向自定义的删除器,或者直接填指定删除器函数的指针类型。
unique_ptr
指定删除器比shared_ptr
复杂,需要在模板参数里面申明指定删除器的类型。
这样做的原因在于,unique_ptr
绑定删除器是在编译期,删除器的类型本身变成于unique_ptr
实例的一部分,因此避免了运行时绑定的时间损耗,这是 unique_ptr
的 0 额外开销的特性决定的。
shared_ptr
的指定删除器在运行时绑定,使用起来更简单;
由于它本身的引用计数已经有运行时的消耗了,再增加一些也无所谓,因此做出了不同的设计。
若使用 Lambda表达式 指定删除器,decltype
改为填写 Lambda表达式 的类型。
int main( ){
unique_ptr<Person, function<void(Person *)>> uPtr{cusAlloc(18), [&](Person *p){
cout << "Deallocating "; p->getAge();
delete p;
}};
cout << endl << "Before main return" << endl;
return 0;
}
在 Lambda表达式 没有捕获任何变量时,是可以直接转换为函数指针的。
因此当指定删除器的 Lambda表达式 没有捕获任何变量时,可以用函数指针代替。
unique_ptr<Person, void(*)(Person *)> uPtr{cusAlloc(18), [](Person *p){
cout << "Deallocating "; p->getAge();
delete p;
}};
但是 Lambda表达式 捕获了任一变量,就需要使用可调用对象包装器来处理声明的函数指针std::function<>
填入,如上一个实例代码。
unique_ptr
与函数
不能按值传递
int main(){
auto func = [](unique_ptr<int> uPtr){
cout << "value = " << *uPtr << endl;
};
auto uPtr = make_unique<int>(100);
func(uPtr);
return 0;
}
按值传递,函数会复制一份参数,因此传入的uPtr
会被复制一份,但这是与独占管理所有权冲突,会编译错误。
使用std::move()
按值传递
int main(){
auto func = [](unique_ptr<int> uPtr){
cout << "value = " << *uPtr << endl;
};
auto uPtr = make_unique<int>(100);
func(move(uPtr));
return 0;
}
/** 输出:
value = 100
**/
使用std::move()
,就能解决单单按值传递的错误了。
传递原始指针代替想按值传入unique_ptr
int main(){
auto func = [](Person *p){
p->getAge();
};
auto uPtr = make_unique<Person>(100);
func(uPtr.get());
cout << endl << "Before main return" << endl;
return 0;
}
/** 输出:
Constructor: person's age = 100
Person's age = 100
Before main return
Destructor: person's age = 100
**/
按引用传递
int main(){
auto func = [](unique_ptr<int> &uPtr){
cout << "value = " << *uPtr << endl;
};
auto uPtr = make_unique<int>(100);
func(uPtr);
return 0;
}
/** 输出:
value = 100
**/
按引用传递,解决单单按值传递的错误。
按引用传递,但为const
int main(){
auto func = [](const unique_ptr<int> &uPtr){
cout << "value = " << *uPtr << endl;
uPtr.reset(); // error
uPtr.reset(new Person()); // error
uPtr.release(); // error
};
auto uPtr = make_unique<int>(100);
func(uPtr);
return 0;
}
/** 输出:
value = 100
**/
使用const
的引用传递,不能改变unique_ptr
所管理的对象是哪一个,使用.reset()
或.release()
等都会造成编译错误。
返回值为unique_ptr
int main(){
auto createUPtr = [](int i) -> unique_ptr<Person>{
unique_ptr<Person> uPtr = make_unique<Person>(i);
return uPtr;
};
unique_ptr<Person> uPtr = createUPtr(100);
uPtr->getAge();
// 用作链式函数
createUPtr(200)->getAge();
cout << endl << "Before main return" << endl;
return 0;
}
/** 输出:
Constructor: person's age = 100
Person's age = 100
Constructor: person's age = 200
Person's age = 200
Destructor: person's age = 200
Before main return
Destructor: person's age = 100
**/
return
可以不使用std::move()
。因为在某些情况下,编译器会自动识别,此处的函数返回不能用复制构造函数,自动使用了移动构造函数。
也可见当用作链式函数时,使用完毕后,unique_ptr
会被销毁,同时被管理的对象也被析构。
unique_ptr
与shared_ptr
- 不能将
shared_ptr
转为unique_ptr
- 可以将
unique_ptr
转为shared_ptr
将函数返回设置成unique_ptr
是一种常见的设计模式,这样可以提高代码的复用度;
同时,可以随时将其改变为shared_ptr
。
通过std::move()
int main(){
unique_ptr<Person> uPtr1 = make_unique<Person>(18);
uPtr1->getAge();
shared_ptr<Person> sPtr1 = move(uPtr1); // unique_ptr转为shared_ptr
sPtr1->getAge();
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << endl << "Before main return" << endl;
return 0;
}
/** 输出:
Constructor: person's age = 18
Person's age = 18
Person's age = 18
sPtr1 use count = 1
Before main return
Destructor: person's age = 18
**/
通过shared_ptr
的构造函数
template<class Y, class Deleter>
shared_ptr(std::unique_ptr<Y,Deleter>&& r);
将函数返回设置成unique_ptr
// 将函数返回设置成unique_ptr
unique_ptr<Person> returnUPtr(int age){
return make_unique<Person>(age);
}
int main(){
shared_ptr<Person> sPtr1 = returnUPtr(18);
sPtr1->getAge();
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << endl << "Before main return" << endl;
return 0;
}
/** 输出:
Constructor: person's age = 18
Person's age = 18
sPtr1 use count = 1
Before main return
Destructor: person's age = 18
**/