IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++学习 一、shared_ptr使用 -> 正文阅读

[C++知识库]C++学习 一、shared_ptr使用

前言

本篇开启C++专题。本来应该从最基础的数据类型开始,但为了服务于看代码,写到哪算哪。

C++11提供了智能指针类,包括unique_ptr,shared_ptr,weak_ptr三种,用以自动释放内存、减少内存泄漏和保障线程安全。首先从shared_ptr开始。

shared_ptr概述

shared_ptr即共享指针,因此多个shared_ptr对象可以与同一个指针相关联。

shared_ptr内部具有两个指针:

  1. 指向对象
  2. 指向控制引用计数的资源

第一个指针与普通指针无异,第二个指针用于计数自身被引用次数。

shared_ptr的构造函数会开辟出新的一片引用计数的资源,引用计数=1;拷贝构造函数不会开辟新的资源,而是使引用计数+1;当shared_ptr对象被销毁时,其相关的引用计数-1;当引用计数为0时,通过delete释放指针指向的内存,销毁指针。

创建shared_ptr对象

0 声明shared_ptr对象

std::shared_ptr<double> p1;

1 通过堆指针创建shared_ptr对象

std::shared_ptr<double> p2(new double(2.));

2 通过拷贝构造函数创建shared_ptr对象

std::shared_ptr<double> p3(p2);
// 等价于
std::shared_ptr<double> p3 = p2;

3 通过赋值运算符修改shared_ptr

p1 = p3;

4 通过std::make_ptr函数初始化

std::shared_ptr<double> p4 = std::make_shared<double>(3.0);
// 也可以初始化一个空对象
std::shared_ptr<double> p4 = std::make_shared<double>();

几个shared_ptr对象初始化问题

shared_ptr类禁止构造函数的隐式转换

 //std::shared_ptr<double> p3 = new double(3.0); error! forbid implicit conversions

这是因为销毁内部指针时,使用的是delete函数。

不能用栈指针进行初始化

//double x = 5.0;
//std::shared_ptr<double> p5(&x);
// error! invalid pointer

不能将同一普通指针给多个shared_ptr对象初始化

//std::shared_ptr<double> p3(p2.get());
//std::shared_ptr<double> p4(p2.get());

上面的初始化通过p2内的对象指针初始化p3和p4,当p3,p4的生命周期结束时,将会包delete double的错误。

原因是:两次初始化得到的p3,p4的引用计数都是1,首先销毁p4,其引用计数变为0,释放p2.get()指针指向的内存;然后销毁p3,其引用也变为0,再次释放内存。

循环引用问题

shared_ptr还有一个容易出现的循环引用问题,代码如下:

class A;
class B;

class A
{
public:
    std::shared_ptr<B> b_;
public:
    A(){
        std::cout << "construct A" << std::endl;
    }
    ~A(){
        std::cout << "destroy A" << std::endl;
    }
};

class B
{
public:
    std::shared_ptr<A> a_;
public:
    B(){
        std::cout << "construct B" << std::endl;
    }
    ~B(){
        std::cout << "destroy B" << std::endl;
    }
};

// *** circular reference *** ///
std::shared_ptr<A> a(new A());
std::shared_ptr<B> b(new B());
a->b_ = b;
b->a_ = a;

运行以上代码会发现,程序结束时,指针对象a,b的析构函数都没有被调用。

原因:创建a,b对象时,引用计数分别为1;a->b_ = b,对象b的引用计数为2,b->a_ = a,对象a的引用计数为2;销毁对象b,b的引用计数变为1(b_);销毁对象a,a的引用计数变为1(a_),a_,b_仍然没有被销毁,导致内存溢出。

对于这种问题,更好的理解方式是在内存层面上的,这个以后会提到。解决循环引用可以使用weak_ptr,这个将在下篇中学习。

shared_ptr与操作符

shared_ptr拥有和普通指针一样的访问数据方法*,->

std::shared_ptr<double> p2(new double(2.));
*p2;

也重载了=,==,!=,>,<,等赋值和比较操作:

p2 = nullptr;
p2 == nullptr;

但是没有++,–等算术操作。

shared_ptr的成员函数

shared_ptr主要有以下几个成员函数:

reset():重置函数,如果参数为空则把对象变为空指针,引用计数-1;如果参数为一个普通堆指针,则相当于把对象与该指针关联,原引用计数-1,新引用计数+1。

std::shared_ptr<double> p2(new double(2.));
p2.reset();

get()获得内部指针。

swap()用于把两个shared_ptr对象的内部指针做交换。

unique()用于判断与内部指针关联的shared_ptr对象是否唯一。

use_count()用于输出引用计数值。

shared_ptr数组

可以通过shared_ptr定义数组,但shared_ptr的析构函数只会释放数组的第一个对象,导致内存泄漏,因此需要提供删除器。

可以提供C++11提供的删除器default_delete

shared_ptr<A> pVec(new A[10](), std::default_delete<A[]>());

还要注意的是,shared_ptr没有++,–等算术运算符,也没有[]运算符,因此不能通过pVec[0]这样取数组成员值。

测试用代码

#include <iostream>
#include <memory>

class A;
class B;

class A
{
public:
    std::shared_ptr<B> b_;
public:
    A(){
        std::cout << "construct A" << std::endl;
    }
    ~A(){
        std::cout << "destroy A" << std::endl;
    }
};

class B
{
public:
    std::shared_ptr<A> a_;
public:
    B(){
        std::cout << "construct B" << std::endl;
    }
    ~B(){
        std::cout << "destroy B" << std::endl;
    }
};



int main(int argc, char **argv) {
    // *** construct shared ptr *** ///
    std::shared_ptr<double> p1;
    std::shared_ptr<double> p2(new double(2.));
    p1 = p2;
    std::cout << "p1 count: " << p1.use_count() << std::endl;
    p2.reset();
    std::cout << "p1 count: " << p1.use_count() << std::endl;
    // std::shared_ptr<double> p3 = new double(3.0); error! forbid implicit conversions
    std::shared_ptr<double> p3 = std::make_shared<double>(3.0);

    std::shared_ptr<double> p4 = p3;
    std::cout << "p4 unique: " << p4.unique() << std::endl;
    p3.reset(new double(3.0));
    std::cout << "p4 unique: " << p4.unique() << std::endl;

    //double x = 5.0;
    //std::shared_ptr<double> p5(&x);
    // std::cout << "p5: " << *p5 << std::endl; // error! invalid pointer
    std::shared_ptr<double> p5(new double(5.0));
    std::shared_ptr<double> p6 = p5;
    std::shared_ptr<double> p7 = p5;
    std::cout << "p5 count: " << p5.use_count() << std::endl;
    //p2.reset(p5.get()); // error! double free
    std::cout << "p5 count: " << p5.use_count() << std::endl;

    // *** circular reference *** ///
    std::shared_ptr<A> a(new A());
    std::shared_ptr<B> b(new B());
    a->b_ = b;
    b->a_ = a;

    std::cout << p3 << "  " << p5 << std::endl;
    p3.swap(p5);
    std::cout << p3 << "  " << p5 << std::endl;
   
    std::shared_ptr<A> pVec(new A[10], std::default_delete<A[]>());
    std::shared_ptr<int> pVecNum(new int[5](), std::default_delete<int[]>());

    std::cout << pVecNum << std::endl;

    return 1;
}

后记

下篇讲 unique_ptr

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-16 12:51:59  更:2022-01-16 12:53:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:05:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码