C++ 11 创建和使用 unique_ptr

      unique_ptr 是 C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中释放掉关联的原始指针的动态内存,并删除关联的原始指针。这样不管函数正常退出还是发生异常,都会释放delete掉动态内存,不会造成内存的泄露。unique_ptr具有->*运算符重载符,它可以像普通指针一样使用。

     unique_ptr 实现了独享所有权的语义,不共享它的指针。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法,只能移动unique_ptr。一个非空的std::unique_ptr总是拥有它所指向的资源。转移一个std::unique_ptr将会把所有权也从源指针转移给目标指针(源指针被置空)。拷贝一个std::unique_ptr将不被允许,因为如果你拷贝一个std::unique_ptr,那么拷贝结束后,这两个std::unique_ptr都会指向相同的资源,它们都认为自己拥有这块资源(所以都会企图释放)。因此std::unique_ptr是一个仅能移动(move_only)的类型。当需要智能指针用于纯 C++ 对象时,可使用unique_ptr,而当构造 unique_ptr 时,可使用make_unique 函数(C++14引入)。

unique_ptr 独享所有权

unique_ptr对象始终是关联的原始指针的唯一所有者。我们无法复制unique_ptr对象,它只能移动。
由于每个unique_ptr对象都是原始指针的唯一所有者,因此在其析构函数中它直接删除关联的指针,不需要任何参考计数。

 

1、如何创建unique_ptr

a. 创建一个空的 unique_ptr 对象

创建一个空的unique_ptr<int>对象,因为没有与之关联的原始指针,所以它是空的。

std::unique_ptr<int> ptr;

b. 使用原始指针创建 unique_ptr 对象,将一个new 操作符返回的指针传递给unique_ptr的构造函数。

要创建非空的 unique_ptr 对象,需要在创建对象时在其构造函数中传递原始指针,即:

std::unique_ptr<class> ptr(new class());
std::unique_ptr<class> ptr = std::unique_ptr<class>(new class());

不能通过赋值的方法创建对象,下面的这句是错误的

std::unique_ptr<class>  ptr = new class(); // error !!

c. C++14引入了std::make_unique 创建 unique_ptr 对象,可以使用std::make_unique()函数来创建。

// 创建一个unique_ptr实例
std::unique_ptr<int> ptr = std::make_unique<int>(new int);

2、获取被管理对象的指针

使用get()函数获取管理对象的指针。

class *p1 = ptr.get();

3、重置 unique_ptr 对象

在 unique_ptr 对象上调用reset()函数将重置它,即它将释放delete关联的原始指针并使unique_ptr 对象为空。

ptr.reset();

4、检查 unique_ptr 对象是否为空

有两种方法可以检查 unique_ptr 对象是否为空或者是否有与之关联的原始指针。

// 方法1
if(!ptr)
	std::cout<<"ptr is empty"<<std::endl;
// 方法2
if(ptr == nullptr)
	std::cout<<"ptr is empty"<<std::endl;

2、无法进行复制构造和赋值操作

unique_ptr没有copy构造函数,不支持普通的拷贝和赋值操作。

int main() 
{
    // 创建一个unique_ptr实例
    unique_ptr<int> pInt(new int(5));
    unique_ptr<int> pInt2(pInt);    // 报错
    unique_ptr<int> pInt3 = pInt;   // 报错
}

3、可以进行移动构造和移动赋值操作

unique_ptr虽然没有支持普通的拷贝和赋值操作,但却提供了一种移动机制来将指针的所有权从一个unique_ptr转移给另一个unique_ptr。如果需要转移所有权,可以使用std::move()函数。

示例:

int main() 
{
    unique_ptr<int> pInt(new int(5));
    unique_ptr<int> pInt2 = std::move(pInt);    // 转移所有权
    //cout << *pInt << endl; // 出错,pInt为空
    cout << *pInt2 << endl;
    unique_ptr<int> pInt3(std::move(pInt2));
}

4、可以返回unique_ptr

unique_ptr不支持拷贝操作,但却有一个例外:可以从函数中返回一个unique_ptr。

示例:

unique_ptr<int> clone(int p)
{
    unique_ptr<int> pInt(new int(p));
    return pInt;    // 返回unique_ptr
}

int main() {
    int p = 5;
    unique_ptr<int> ret = clone(p);
    cout << *ret << endl;
}

5、unique_ptr 对象不可复制

由于 unique_ptr 不可复制,只能移动。因此,我们无法通过复制构造函数或赋值运算符创建unique_ptr对象的副本。

// 编译错误 : unique_ptr 不能复制
std::unique_ptr<class> Ptr3 = Ptr2; // Compile error

// 编译错误 : unique_ptr 不能复制
Ptr = Ptr2; //compile error

6、转移 unique_ptr 对象的所有权
我们无法复制 unique_ptr 对象,但我们可以转移它们。这意味着 unique_ptr 对象可以将关联的原始指针的所有权转移到另一个 unique_ptr 对象。让我们通过一个例子来理解:

// 通过原始指针创建 Ptr2
std::unique_ptr<class> taskPtr2(new class(55));
// 把taskPtr2中关联指针的所有权转移给taskPtr4
std::unique_ptr<class> Ptr4 = std::move(Ptr2);
// 现在taskPtr2关联的指针为空
if(Ptr2 == nullptr)
	std::cout<<"Ptr2 is  empty"<<std::endl;

// Ptr2关联指针的所有权现在转移到了taskPtr4中
if(Ptr4 != nullptr)
	std::cout<<"Ptr4 is not empty"<<std::endl;

// 会输出55
std::cout<< Ptr4->mId << std::endl;

std::move() 将把 taskPtr2 转换为一个右值引用。因此,调用 unique_ptr 的移动构造函数,并将关联的原始指针传输到 taskPtr4。在转移完原始指针的所有权后, taskPtr2将变为空。

7、释放关联的原始指针

在 unique_ptr 对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。

std::unique_ptr<class> Ptr5(new class(args,...));
// 不为空
if(Ptr5 != nullptr)
	std::cout<<"Ptr5 is not empty"<<std::endl;
// 释放关联指针的所有权
Task * ptr = Ptr5.release();
// 现在为空
if(Ptr5 == nullptr)
	std::cout<<"Ptr5 is empty"<<std::endl;

8、unique_ptr使用场景

8.1 为动态申请的资源提供异常安全保证

我们先来看看下面这一段代码:

void Func()
{
    int *p = new int(5);

    // ...(可能会抛出异常)

    delete p;
}

这是我们传统的写法:当我们动态申请内存后,有可能我们接下来的代码由于抛出异常或者提前退出(if语句)而没有执行delete操作。

解决的方法是使用unique_ptr来管理动态内存,只要unique_ptr指针创建成功,其析构函数都会被调用。确保动态资源被释放。

void Func()
{
    unique_ptr<int> p(new int(5));

    // ...(可能会抛出异常)
}

8.2 返回函数内动态申请资源的所有权

unique_ptr<int> Func(int p)
{
    unique_ptr<int> pInt(new int(p));
    return pInt;    // 返回unique_ptr
}

int main() {
    int p = 5;
    unique_ptr<int> ret = Func(p);
    cout << *ret << endl;
    // 函数结束后,自动释放资源
}

8.3 在容器中保存指针

int main() 
{
    vector<unique_ptr<int>> vec;
    unique_ptr<int> p(new int(5));
    vec.push_back(std::move(p));    // 使用移动语义
}

8.4 管理动态数组

标准库提供了一个可以管理动态数组的unique_ptr版本。

int main() 
{
    unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5});
    p[0] = 0;   // 重载了operator[]
}

总结

new出来的对象是位于堆内存上的,必须调用delete才能释放其内存。
unique_ptr 是一个装指针的容器,且拥有关联指针的唯一所有权,作为普通变量使用时系统分配对象到栈内存上,超出作用域时会自动析构,unique_ptr对象的析构函数中会delete其关联指针,这样就相当于替我们执行了delete堆内存上的对象。

成员函数作用
reset()重置unique_ptr为空,delete其关联的指针。
release()不delete关联指针,并返回关联指针。释放关联指针的所有权,unique_ptr为空。
get()仅仅返回关联指针

unique_ptr不能直接复制,必须使用std::move()转移其管理的指针,转移后原 unique_ptr 为空。

转自:

https://blog.csdn.net/jingchuanhu/article/details/79460548

https://blog.csdn.net/shaosunrise/article/details/85158249

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值