大话C++:第28篇 详解独占智能指针
1 智能指针概述
C++中的智能指针是一种用于管理原生指针(raw pointer)生命周期的对象。智能指针的引入主要是为了帮助开发者避免原生指针在使用过程中可能产生的一些问题,如内存泄漏、野指针(dangling pointer)等。C++11及之后的版本提供了多种智能指针类型,包括:
-
std::unique_ptr:这是一种独占所有权的智能指针,它负责对象的生命周期。当
std::unique_ptr
被销毁时,它所指向的对象也会被自动销毁。这种智能指针保证同一时间只能有一个std::unique_ptr
指向同一个对象。它不能被复制到其他std::unique_ptr
,但可以通过std::move
来转移所有权。 -
std::shared_ptr:这是一种共享所有权的智能指针。多个
std::shared_ptr
可以指向同一个对象,并且它们会共同管理该对象的生命周期。当最后一个指向对象的std::shared_ptr
被销毁时,对象才会被销毁。这种智能指针使用引用计数来实现共享所有权。 -
std::weak_ptr:这是
std::shared_ptr
的一个辅助工具,用于解决std::shared_ptr
可能产生的循环引用问题。std::weak_ptr
可以从一个std::shared_ptr
或另一个std::weak_ptr
对象构造,但它的构造或析构都不会影响引用计数。只有当std::weak_ptr
升级为std::shared_ptr
时,引用计数才会增加。
使用智能指针时,应尽量避免使用原生指针,因为智能指针能自动管理内存,从而减少出错的可能性。当需要将智能指针传递给不支持智能指针的API时,可以使用智能指针的.get()
成员函数获取原生指针,但这种情况下需要格外小心,以避免内存泄漏和其他问题。
2 std::unique_ptr
std::unique_ptr
是 C++11 引入的一种智能指针类型,用于管理动态分配的内存。它表示“独占”所有权的概念,即 std::unique_ptr
所指向的对象在同一时间只能由一个 std::unique_ptr
拥有。当 std::unique_ptr
被销毁时(例如超出作用域或被显式删除),它所指向的对象也会被自动销毁。
std::unique_ptr
的主要操作和成员函数:
操作/成员函数 | 描述 |
---|---|
构造函数 | |
unique_ptr() | 默认构造函数,创建一个空的 unique_ptr ,不拥有任何对象。 |
unique_ptr(pointer p) noexcept | 接收一个原生指针 p ,并接管其所有权。 |
unique_ptr(nullptr_t) noexcept | 接收 nullptr ,创建一个空的 unique_ptr 。 |
unique_ptr(unique_ptr&& u) noexcept | 移动构造函数,从另一个 unique_ptr u 接管对象的所有权。 |
析构函数 | |
~unique_ptr() | 析构函数,销毁 unique_ptr 所拥有的对象,并释放内存。 |
赋值操作 | |
unique_ptr& operator=(pointer p) noexcept | 重置 unique_ptr 以拥有原生指针 p 指向的对象。 |
unique_ptr& operator=(nullptr_t) noexcept | 重置 unique_ptr 为空,释放当前拥有的对象。 |
unique_ptr& operator=(unique_ptr&& u) noexcept | 移动赋值,从另一个 unique_ptr u 接管对象的所有权。 |
重置操作 | |
void reset(pointer p = pointer()) noexcept | 重置 unique_ptr 以拥有原生指针 p 指向的对象,或如果 p 是 nullptr ,则释放当前对象。 |
观察操作 | |
pointer get() const noexcept | 返回 unique_ptr 所拥有的对象的原生指针。 |
operator bool() const noexcept | 隐式转换为 bool ,检查 unique_ptr 是否拥有一个对象(非空)。 |
reference operator*() const | 解引用 unique_ptr 所拥有的对象。 |
pointer operator->() const noexcept | 返回指向 unique_ptr 所拥有的对象的原生指针。 |
释放操作 | |
pointer release() noexcept | 释放 unique_ptr 对对象的所有权,并返回原生指针。此后,unique_ptr 不再拥有该对象。 |
注意:pointer
、reference
和 nullptr_t
是类型别名,分别代表 T*
、T&
和 std::nullptr_t
,其中 T
是 unique_ptr
所管理的对象类型。
综合案例,代码示例如下:
#include <iostream>
#include <memory>
#include <string>
class Data
{
public:
// 构造函数
Data(const std::string& value)
: value(value)
, addr(new std::string(value))
{
std::cout << "调用Data构造函数" << std::endl;
}
// 拷贝构造函数
Data(const Data& other)
{
value = other.value;
// 注意,深拷贝
addr = new std::string(*other.addr);
}
// 赋值运算符
Data& operator=(const Data& other)
{
if (this != &other)
{
value = other.value;
// 注意,深拷贝
addr = new std::string(*other.addr);
}
return *this;
}
// 析构函数
~Data()
{
std::cout << "调用析构函数" << std::endl;
std::cout << value << std::endl;
// 注意,如果类里采用了原生指针的话,new的对象,需要在析构函数里手动释放
if (addr != nullptr)
{
delete addr;
addr = nullptr;
}
}
std::string GetValue() const
{
return value;
}
void SetValue(const std::string& value)
{
this->value = value;
}
void Show() const
{
std::cout << "value=" << value << std::endl;
}
private:
std::string value;
std::string *addr;
};
int main()
{
// 创建unique_ptr对象
// 1.new创建对象
std::unique_ptr<Data> uptr1(new Data("new obj1"));
// 2.std::make_unique,推荐方式
std::unique_ptr<Data> uptr2 = std::make_unique<Data>("make_unique obj2");
// unique_ptr对象赋值给另外一个对象呢?
// 典型禁用了unique_ptr的赋值运算符operator=
// errror:use of deleted function ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=
// (const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Data; _Dp = std::default_delete<Data>]’
// std::unique_ptr<Data> uptr4;
// uptr4 = uptr2;
// 指针形式,访问对象的成员函数
uptr1->Show();
uptr2->Show();
// 解引用unique_ptr
// 相当于对象自身
// 例如,Data obj;
// obj.GetValue()
std::cout << "uptr1对象的内容:" << (*uptr1).GetValue() << std::endl;
// 获取原生指针
Data *rawPtr = uptr1.get();
std::cout << "原生指针指向对象的内容:" << rawPtr->GetValue() << std::endl;
// 对象控制权的转移
// 利用移动构造函数和移动语义std::move
std::unique_ptr<Data> uptr3 = std::move(uptr2);
uptr3->Show();
// 对象释放
// 1.reset释放原有对象,要么指向nullptr,要么重新指向新的对象
uptr3.reset(new Data("reset obj3"));
uptr3->Show();
// 2.release()释放原有对象
uptr3.release();
if (uptr3 != nullptr)
{
std::cout << "uptr3对象没有被释放了" << std::endl;
}
else
{
std::cout << "uptr3对象已经被释放了" << std::endl;
}
return 0;
}