智能指针之unique_ptr(详解)

shared_ptr指针详解:shared_ptr智能指针详解

weak_ptr指针详解:弱类型指针weak_ptr详解

智能指针之unique_ptr

指针的创建和初始化

代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr2 = new int(11);  
    unique_ptr<int> ptr = make_unique<int>(10), ptr1(ptr2);  
    cout << *ptr1 << endl;  
    cout << *ptr2 << endl;  
} 

 

运行结果:

 

我们看到初始化unique_ptr指针的方法有两种:

① 调用make_unique<type>(value)在定义进行赋值;

② 与shared_ptr和auto_ptr一样,可以使用调用new/new[]返回的指针去初始化unique_ptr指针;

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int[]> ptr(new int[2]{ 1,2 });  
    cout << ptr[0] << endl; // 输出1  
    cout << ptr[1] << endl; // 输出2  
}  

 

但是,以下的初始化方式是不对的:

① 不可以使用unique_ptr,shared_ptr,auto_ptr和weak_ptr初始化/赋值给unique_ptr指针;

代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    shared_ptr<int> ptr = make_shared<int>(10);  
    weak_ptr<int> ptr1(ptr);  
    auto_ptr<int> ptr2(new int(11));  
    unique_ptr<int> ptr3(ptr), ptr4(ptr1), ptr5(ptr2);  // 错误的初始化方式
} 

 

错误原因:

 

② 尽量不要让unique_ptr指针去维护已经使用new返回值初始化的指针;

代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr = new int(10);  
    unique_ptr<int> ptr1(ptr); // 可以,但最好不要这样  
    unique_ptr<int> ptr2(new int(11)); // 这样最好  
}  

 

这样做的好处:

unique_ptr强调在整个作用域中的唯一性,也就是说“在其作用域内,有且仅有一个指针去维护这片堆区内存空间”,如果我们使用new返回值初始化过的int*类型的指针去初始化unique_ptr,那就可能会发生如下状况:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr = new int(10);  
    unique_ptr<int> ptr1(ptr); // 使用ptr初始化unique_ptr指针  
    shared_ptr<int> ptr2(ptr); // 又使用ptr初始化shared_ptr指针  
}  

 

错误提示:

(内存重复delete释放的错误提示)

 

为啥会导致这种情况,就是因为我们将ptr指针初始化unique_ptr指针后,结果变成了unique_ptr指针与ptr指针共同维护ptr指向的堆区内存空间,此时,就会发现一个重要的问题缺陷“ptr再赋值给unique_ptr指针后也可以初始化其它类型的指针”,这严重违背了“unique_ptr指针在其作用域中的唯一性”。希望这种情况尽量不要发生,否则会因为不经意间再次使用ptr导致内存重复释放程序导致崩溃。

unique_ptr赋值时的注意事项

① 向函数传入unique_ptr参数时,必须要使用“引用传参”

⑴ 正确代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
void ShowInf(unique_ptr<int>& obj)  // 引用传参
{  
    cout << *obj << endl;  
}  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    ShowInf(ptr); // 输出10  
}  

 

⑵ 为什么不可以使用值传递的方式进行函数参数的传递?

我们知道“unique_ptr禁止一切想与其共享内存所有权的行为”,进行值传递不就是将指针指向的地址拷贝至一个“作用域仅在函数之内的临时指针变量”吗,只要是拷贝就是意味着有任要分走unique_ptr对于这篇内存区域的所有权,这是坚决禁止的行为!

② 使用new返回的堆区指针去在定义时初始化unique_ptr指针可以,但是在定义完成后,使用new返回的堆区指针是不可以赋值给unique_ptr指针。

错误代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    //ptr = ptr1; // 错误代码  
}  

 

错误提示:

 

正确代码:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    ptr = make_unique<int>(10);  
}  

 

给unique_ptr赋值的唯一选项:使用make_unique<type>(value)。其实make_unique底层也是使用new来动态申请内存空间并返回一个unique_ptr类型的指针。

③ 由于unique_ptr独一无二的特性,即在其生命周期内只能指向一片内存区域,因此unique_ptr类型的智能指针不可复制!由于 unique_ptr 不可复制,只能移动。因此,我们无法通过复制构造函数或赋值运算符创建unique_ptr对象的副本。当你使用unique_ptr的任意一类复制构造函数或重载的赋值运算符时,编译器都会告诉你“对应的成员函数已经被delete掉了,不要再调用了”。

// 编译错误 : unique_ptr 不能复制  
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error  
  
// 编译错误 : unique_ptr 不能复制  
taskPtr = taskPtr2; //compile error  

 

unique_ptr成员函数解析

① get函数:返回unique_ptr维护的指针地址

⑴ 函数作用:

返回unique_ptr维护的指针地址。

⑵ 代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    ptr = make_unique<int>(10);  
  
    cout << *ptr.get() << endl; // 返回10  
}  

 

② release函数:释放其关联的原始指针的所有权,并返回原始指针

⑴ 函数作用:

释放其关联的原始指针的所有权,并返回原始指针。

⑵ 函数示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    int* ptr1 = ptr.release();  // 返回与unique_ptr类型相同类型的指针
    if (ptr == nullptr)  
    {  
        cout << "unique_ptr类型的指针ptr已被置空,释放了内存的控制权" << endl;  
    }  
    if (ptr1 != nullptr)  
    {  
        cout << "原本unique_ptr指向的内存空间并没有被释放掉" << endl;  
    }  
}  

 

输出结果:

 

③ reset函数:释放指向的内存,并且释放了对这块内存的所有权

⑴ 函数作用:

释放指向的内存,并且释放了对这块内存的所有权。

⑵ 代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    cout << "ptr复位前:" << *ptr << endl;  
    ptr.reset(new int(100)); // 使得ptr指向一块新的内存空间,同时释放掉原来指向的内存空间  
    cout << "ptr复位后:" << *ptr << endl;  
    ptr.reset(nullptr); //与ptr.reset()效果等价,都是使得ptr指向nullptr  
    cout << "ptr是否被置空:" << (ptr == nullptr) << endl;  
} 

 

运行结果:

 

⑶ 结果分析:

其实这里的reset成员函数与shared_ptr的reset成员函数相同之处在于:都是改变指针指向的内存空间,但是不一样的是:由于只有一个unique_ptr指针指向这片在堆区开辟的内存区域,因此当指针指向其他内存空间时,这片原来的内存空间会被释放掉。因此,我常常称“unique_ptr指针的reset函数拥有的功能”为“指针的破坏功能,即传说中的损人利己”。

 

④ swap函数:交换两个unique_ptr指针指向区域(交换地址)

⑴ 函数功能:

交换两个unique_ptr指针指向区域(交换地址)

⑵ 代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10), ptr1(nullptr);  
    cout << "交换前:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
    ptr.swap(ptr1);  
    cout << "交换后:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
}  

 

运行结果:

 

结果显示:ptr与ptr1两个unique_ptr<int>类型的指针指向的地址已经实现了交换。

⑤ get_deleter函数:返回自定义的删除器

⑴ 函数作用:

我们知道,当一个智能指针生命结束或者将所指向的内存空间释放(执行了unique_ptr::reset()成员函数)时,都会调用删除器去删除指针所指的堆区内存空间,如果这个操作由系统掌管,我们只能根本无法得知内存到底有没有被有效的释放,或者说我们想要将“内存释放的权力” 掌控在自己手中(比如:我们可以统计系统到底通过unique_ptr指针释放了几次内存……等更加人性化的操作),这样有利于我们统计一些内存释放的信息,根据这些信息对代码进行优化从而优化代码使得代码的运行效率达到极致。

⑵ 代码示例:

如下代码展示了“系统到底通过ptr指针释放了几次内存”:

#include <iostream>  
#include <memory>  
using namespace std;  
  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    template <typename T>  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    unique_ptr<int, deleter> object_a(new int(1));  
    object_a.reset(new int(2));  
    object_a.get_deleter().ShowInf();  
}  

 

运行结果:

 

第一次是因为我们调用了reset成员函数去改变了ptr指向的内存空间同时释放了ptr原来指向的内存区域;第二次是我们调用了get_deleter()无参成员函数返回的deleter类的匿名对象(临时对象)去调用类的共有成员函数(类的外部接口)去显示“目前为止,系统到底通过ptr指针释放了几次堆区内存”;第三次是因为ptr指针的生命周期结束需要通过调用ptr的删除器释放ptr指向的内存空间,这里我们自定义了删除器将删除ptr指向内存的权力牢牢地把握在自己手中,当ptr释放内存时,就会调用我们的仿函数deleter()。

此外,我们还可以在多个unique_ptr类型的智能指针中使用同一个删除器对象,从而统计代码运行过程中,多个或全部unique_ptr类型的智能指针删除指向内存区域的总次数。代码示例如下:

#include <iostream>  
#include <memory>  
using namespace std;  
  
template <typename T>  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    unique_ptr<int, deleter<int> > object_a(new int(1));  
    unique_ptr<int, deleter<int>& > object_b(new int(3), object_a.get_deleter());  
}  

 

运行结果如下:

 

我们可以看到:object_a和object_b共用一个删除器,这样我们就可以统计object_a和object_b两个unique_ptr类型的指针总共释放内存的次数。我们可以就此推广至:“统计整个程序中unique_ptr类型的指针释放内存的次数”。

也可以用如下这种代码格式去统计系统通过object_a和object_b指针释放内存的次数:

#include <iostream>  
#include <memory>  
using namespace std;  
  
template <typename T>  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    deleter<int> del;  
    unique_ptr<int, deleter<int>& > object_a(new int(1), del);  // 传入del这个deleter类型的对象本身
    unique_ptr<int, deleter<int>& > object_b(new int(3), del);  // 传入del这个deleter类型的对象本身
}  

 

如何转移unique_ptr对象的所有权?

代码示例:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr(new int(10));  
    cout << "调用移动语义前:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    unique_ptr<int> ptr1 = move<unique_ptr<int>& >(ptr);  
    cout << "ptr指向是否为空:" << (ptr == nullptr) << endl;  
    cout << "ptr1指向的元素为" << *ptr1 << endl;  
    cout << "使用移动语义后:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
}  

 

运行结果:

 

我们从结果就可以看出:移动语义的结果就是把一个unique_ptr中存储的地址转移至另一个unique_ptr指针中去,并且把源unique_ptr类型的指针置空(=nullptr)。这样做比较安全,但是我们使用指针前一定要检查以下指针是否非空以防万一。

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥肥胖胖是太阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值