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也就不能支持复制操作。

常用初始化及方法

  1. unique_ptr的常用初始化基本与shared_ptr有重合。
    • 但是不能使用复制构造函数,可以使用移动构造函数
    • 但对应的make_unique是在 C++14 起提出的
  2. 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_ptrshared_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

**/
posted @ 2023-01-17 19:08  Champrin  阅读(239)  评论(0编辑  收藏  举报