【C++11智能指针】shared_ptr的初始化、拷贝构造和拷贝赋值、移动构造和移动赋值

1.智能指针概述

直接用 new 返回的指针称为“裸指针”,智能指针可以理解为对“裸指针”进行了包装,解决“裸指针”可能带来的各种问题。

智能指针最突出的优点是能够自动释放所指向的对象内存,帮助我们进行动态分配对象(new出来的对象)的生命周期的管理,能够有效防止内存泄漏。

C++标准库有四种智能指针:

  • auto_ptr:C++98,目前 auto_ptr 已经完全被 unique_ptr 取代,C++11标准中不推荐使用(弃用)auto_ptr。

  • unique_ptr:C++11,独占式指针。同一个时间内,只有一个指针能够指向该对象。当然,该对象的所有权还是可以移交出去的。

  • shared_ptr:C++11,共享式指针。多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放。

  • weak_ptr:C++11,weak_ptr 是辅助 shared_ptr 工作的。

2.shared_ptr的初始化

2.1 shared_ptr和new结合使用(直接初始化)

unique_ptrshared_ptrweak_ptr 都是类模板,因此,当我们创建一个智能指针时,必须提供额外的信息,即指针可以指向的类型。与 vector 一样,我们在尖括号内给出类型,之后是所定义的这种智能指针的名字:

shared_ptr<string> p1; // shared_ptr可以指向string
shared_ptr<list<int>> p2; // shared_ptr可以指向int的list

默认初始化的智能指针中保存着一个空指针。

shared_ptr<int> pi; // 指向int的空智能指针,pi==nullptr
shared_ptr<int> pi(new int(100)); // pi指向一个值为100的int型数据

接受指针参数的智能指针构造函数是 explicit 的,因此,我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针:

shared_ptr<int> p1 = new int(1024); // 错误
shared_ptr<int> p2(new int(1024)); // 正确

出于相同的原因,一个返回 shared_ptr 的函数不能在其返回语句中隐式转换一个普通指针:

shared_ptr<int> clone(int p) {
	return new int(p); // 错误
}

我们必须将 shared_ptr 显式绑定到一个想要返回的指针上:

#include<iostream>
using namespace std;

shared_ptr<int> clone(int p) {
	return shared_ptr<int>(new int(p)); // 正确
}

int main() {

	shared_ptr<int> pi3 = clone(130);
	
	return 0;
}

2.2 make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为 make_shared 的标准库函数。

此函数在动态内存(堆)中分配一个对象并初始化它,返回指向此对象的 shared_ptr。

与智能指针一样,make_shared 也定义在头文件 memory 中。

当要用 make_shared 时,必须指定想要创建的对象的类型。定义方式与模板类相同,在函数名之后跟一个尖括号,在其中给出类型:

// 指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
// p4指向一个值为"9999999999"的string
shared_ptr<string> p4 = make_shared<string>(10, '9');

make_shared 用其参数来构造给定类型的对象。例如,调用 make_shared<string> 时传递的参数必须与 string 的某个构造函数相匹配,调用 make_shared<int> 时传递的参数必须能用来初始化一个 int,依此类推。

如果我们不传递任何参数,对象就会进行值初始化。

// p5指向一个值初始化的int,即值为0
shared_ptr<int> p5 = make_shared<int>();

当然,我们通常用 auto 定义一个对象来保存 make_shared 的结果,这种方式较为简单:

// p6指向一个动态分配的空vector<string>
auto p6 = make_shared<vector<string>>();

3.shared_ptr的拷贝构造和拷贝赋值

当进行拷贝或赋值操作时,每个 shared_ptr 都会记录有多少个其他 shared_ptr 指向相同的对象:

auto p = make_shared<int>(42); // p指向的对象只有p一个引用者
auto q(p); // p和q指向相同对象,此对象有两个引用者

我们可以认为每个 shared_ptr 都有一个关联的计数器,通常称其为引用计数(reference count)

无论何时我们拷贝一个 shared_ptr,计数器都会递增。例如,当用一个 shared_ptr 初始化另一个 shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。

当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁(例如一个局部的 shared_ptr 离开其作用域)时,计数器就会递减。

一旦一个 shared_ptr 的计数器变为 0,它就会自动释放自己所管理的对象:

auto r = make_shared<int>(42); // r指向的int只有一个引用者
r = q;
// 给r赋值,令它指向另一个地址
// 递增q指向的对象的引用计数
// 递减r原来指向的对象的引用计数
// r原来指向的对象已没有引用者,会自动释放

在上面代码中,我们分配了一个 int,将其指针保存在 r 中。接下来,我们将一个新值赋予 r。在此情况下,r 是唯一指向此 int 的 shared_ptr,在把 q 赋给 r 的过程中,此 int 被自动释放。

提示:到底是用一个计数器还是其他数据结构来记录有多少指针共享对象,完全由标准库的具体实现来决定。关键是智能指针类能记录有多少个 shared_ptr 指向相同的对象,并能在恰当的时候自动释放对象。

4.shared_ptr的移动构造和移动赋值

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp1(new int(180)); // 强引用计数从0变1
    
    shared_ptr<int> sp2(std::move(sp1)); // 移动构造一个新的智能指针对象sp2,sp1变成空,sp2指向该内存,强引用计数仍为1

	return 0;
}
#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp1(new int(180)); // 强引用计数从0变1

    shared_ptr<int> sp2;

    sp2 = std::move(sp1); // 移动赋值,sp1变成空,sp2指向该内存,强引用计数仍为1

	return 0;
}

5.shared_ptr自动销毁所管理的对象

当指向一个对象的最后一个 shared_ptr 被销毁时,shared_ptr 类会自动销毁此对象。它是通过析构函数完成销毁工作的。

类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制此类型的对象销毁时做什么操作。析构函数一般用来释放对象所分配的资源。例如,string 的构造函数会分配内存来保存构成 string 的字符,string 的析构函数就负责释放这些内存。类似的,vector 的若干操作都会分配内存来保存其元素,vector 的析构函数就负责销毁这些元素,并释放它们所占用的内存。

shared_ptr 的析构函数会递减它所指向的对象的引用计数。如果引用计数变为 0 0 0,shared_ptr 的析构函数就会销毁对象,并释放它占用的内存。

6.shared_ptr自动释放相关联的内存

当动态对象不再被使用时,shared_ptr 类会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。

例如,在下面代码中,create() 函数返回一个 shared_ptr,指向一个 int 类型的动态分配的对象,对象是通过一个类型为 int 的参数进行初始化的。由于 create() 函数返回一个 shared_ptr,所以我们可以确保它分配的对象会在恰当的时刻被释放。

// create()函数返回一个shared_ptr,指向一个动态分配的对象
shared_ptr<int> create(int value)
{
    return make_shared<int>(value); // shared_ptr负责释放内存
}

例如,在下面代码中,fun() 函数将 create() 函数返回的 shared_ptr 保存在局部变量 p 中,由于 p 是 fun() 函数的局部变量,在 fun() 函数结束时它将被销毁。当 p 被销毁时,将递减其引用计数并检查它是否为 0 0 0。在此例中,p 是唯一引用 create() 函数返回的内存的对象。由于 p 将要销毁,p 指向的这个对象也会被销毁,所占用的内存会被释放。

void fun(int value)
{
    shared_ptr<int> p = create(value); // p离开了作用域,它指向的内存会被自动释放掉
    return;
}

int main()
{
    fun(12);
    return 0;
}

但如果有其他 shared_ptr 也指向这块内存,它就不会被释放掉。在下面代码中,fun() 函数中的 return 语句向此函数的调用者返回一个 p 的拷贝。拷贝一个 shared_ptr 会增加所管理对象的引用计数值。现在当 p 被销毁时,它所指向的内存还有其他使用者。对于一块内存,shared_ptr 类保证只要有任何 shared_ptr 对象引用它,它就不会被释放掉。

shared_ptr<int> fun(int value)
{
    shared_ptr<int> p = create(value);
    return p; // 当我们返回p时,引用计数进行了递增操作
} // p离开了作用域,但它指向的内存不会被释放掉

int main()
{
    auto sp = fun(12);
    return 0;
}

由于在最后一个 shared_ptr 销毁前内存都不会释放,保证 shared_ptr 在无用之后不再保留就非常重要了。如果你忘记了销毁程序中不再需要的 shared_ptr,程序仍会正确执行,但会浪费内存。shared_ptr 在无用之后仍然保留的一种可能情况是,你将 shared_ptr 存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,你应该确保用 erase 删除那些不再需要的 shared_ptr 元素。

提示:如果你将 shared_ptr 存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用 erase 删除不再需要的那些元素。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shared_ptr拷贝构造移动构造分别用于创建一个新的shared_ptr对象,并与原始shared_ptr对象共享同一个资源。拷贝构造函数会增加资源的引用计数,而移动构造函数则会将原始shared_ptr对象的资源转移到新的shared_ptr对象上。 在C++11中,shared_ptr拷贝构造函数和拷贝赋值运算符都是使用引用计数的方式来实现资源的共享。当一个shared_ptr对象通过拷贝构造赋值给另一个shared_ptr对象时,引用计数会递增,表示有多个shared_ptr对象共享同一个资源。只有当所有与资源关联的shared_ptr对象都被销毁时,资源才会被释放。 而移动构造函数则是在C++11中引入的,它允许将资源的所有权从一个shared_ptr对象转移给另一个shared_ptr对象,而不需要增加引用计数。移动构造函数通过将原始shared_ptr对象的引用计数置零,并将资源指针转移给新的shared_ptr对象,实现了资源的转移。 引用提到了shared_ptr拷贝构造拷贝赋值的相关内容。引用提到了weak_ptr作为shared_ptr的辅助工具。引用提到了unique_ptr移动构造函数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【C++11智能指针shared_ptr初始化拷贝构造拷贝赋值移动构造移动赋值](https://blog.csdn.net/qq_42815188/article/details/99410218)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [C++11unique-ptr智能指针详解.pdf](https://download.csdn.net/download/qq_43934844/87504581)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值