Qt智能指针之QScopedPointer

2023-11-18

内存释放的问题是C++中比较头疼的问题,合理的使用智能指针能有效的帮助我们减少忘记释放内存,导致的内存泄露问题。本文以Qt中的QScopedPointer为例,通过讲解其用法,从源码深度剖析其实现方式。
QScopedPointer的使用原理比较简单,实际上就是通过QScopedPointer类型,记录申请的某一片内存空间的地址,在QScopedPointer类型变量生命周期结束时,会自动调用QScopedPointer的析构函数,从而达到自动释放堆上申请的内存空间的目的。

一、demo 及基本使用技巧

#include <QCoreApplication>
#include <QScopedPointer>
#include <QDebug>


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    /// 默认使用QScopedPointerDeleter 释放资源
    QScopedPointer<int> si(new int(3));
    qDebug() << *si.data();

    /// 显示指定使用默认使用QScopedPointerDeleter 释放资源
    QScopedPointer<double,QScopedPointerDeleter<double>> sd(new double(8.88));
    qDebug() << *sd.data();

    /// 显示指定使用QScopedPointerPodDeleter 释放资源
    QScopedPointer<int,QScopedPointerPodDeleter> fi((int*)malloc(sizeof (int)));
    *fi = 30;
    qDebug() << *fi;

    /// 数组类型
    QScopedArrayPointer<int,QScopedPointerArrayDeleter<int>> asi(new int[3]{4,1,2});
    for(int i = 0;i < 3;++i) qDebug() << asi[i];

    return a.exec();
}

template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer{
	// ... 
};

QScopedPointer 是一个模板类,其包含两个模板参数,第一个参数是堆上变量的数据类型(可以是自定义数据类型),第二个参数是内存释放的类,该类中定义了一个静态的内存清理成员函数,如下。

static inline void cleanup(T *pointer)
{
	// clean up here...
}

cleanup函数中具体制定了内存释放的方法:
如new 方法申请的内存,对应释放内存的方式为delete;
new 数组申请的内存,对应释放内存的方式是delete[ ];
malloc 申请的内存,对应的释放内存的方式为 free 等等

清楚了QScopedPointer最基本的参数传入规则,结合我们给出的demo,很容易掌握基本使用技巧。

二、自定义类型内存释放

// this struct calls "myCustomDeallocator" to delete the pointer
  struct ScopedPointerCustomDeleter
  {
      static inline void cleanup(MyCustomClass *pointer)
      {
      		// TODO : add customed clean up logics here
         	//  myCustomDeallocator(pointer);
      }
  };

  // QScopedPointer using a custom deleter:
  QScopedPointer<MyCustomClass, ScopedPointerCustomDeleter> customPointer(new MyCustomClass);
 

自定义数据类型的指定内存释放方式,可以自定义一个析构器类,并在类中实现静态成员函数 cleanup( ) ,在QScopedPointer 构造智能指针变量时,第二个参数显示指定内存释放的“析构器类”类型即可。

三、从源码的角度分析QScopedPointer 智能指针的实现方式

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QSCOPEDPOINTER_H
#define QSCOPEDPOINTER_H

#include <QtCore/qglobal.h>

#include <stdlib.h>

QT_BEGIN_NAMESPACE

template <typename T>
struct QScopedPointerDeleter
{
    static inline void cleanup(T *pointer)
    {
        // Enforce a complete type.
        // If you get a compile error here, read the section on forward declared
        // classes in the QScopedPointer documentation.
        typedef char IsIncompleteType[ sizeof(T) ? 1 : -1 ];
        (void) sizeof(IsIncompleteType);

        delete pointer;
    }
};

template <typename T>
struct QScopedPointerArrayDeleter
{
    static inline void cleanup(T *pointer)
    {
        // Enforce a complete type.
        // If you get a compile error here, read the section on forward declared
        // classes in the QScopedPointer documentation.
        typedef char IsIncompleteType[ sizeof(T) ? 1 : -1 ];
        (void) sizeof(IsIncompleteType);

        delete [] pointer;
    }
};

struct QScopedPointerPodDeleter
{
    static inline void cleanup(void *pointer) { if (pointer) free(pointer); }
};

#ifndef QT_NO_QOBJECT
template <typename T>
struct QScopedPointerObjectDeleteLater
{
    static inline void cleanup(T *pointer) { if (pointer) pointer->deleteLater(); }
};

class QObject;
typedef QScopedPointerObjectDeleteLater<QObject> QScopedPointerDeleteLater;
#endif

template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer
{
    typedef T *QScopedPointer:: *RestrictedBool;
public:
    explicit QScopedPointer(T *p = nullptr) noexcept : d(p)
    {
    }

    inline ~QScopedPointer()
    {
        T *oldD = this->d;
        Cleanup::cleanup(oldD);
    }

    inline T &operator*() const
    {
        Q_ASSERT(d);
        return *d;
    }

    T *operator->() const noexcept
    {
        return d;
    }

    bool operator!() const noexcept
    {
        return !d;
    }

#if defined(Q_QDOC)
    inline operator bool() const
    {
        return isNull() ? nullptr : &QScopedPointer::d;
    }
#else
    operator RestrictedBool() const noexcept
    {
        return isNull() ? nullptr : &QScopedPointer::d;
    }
#endif

    T *data() const noexcept
    {
        return d;
    }

    T *get() const noexcept
    {
        return d;
    }

    bool isNull() const noexcept
    {
        return !d;
    }

    void reset(T *other = nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval<T *>())))
    {
        if (d == other)
            return;
        T *oldD = d;
        d = other;
        Cleanup::cleanup(oldD);
    }

    T *take() noexcept
    {
        T *oldD = d;
        d = nullptr;
        return oldD;
    }

    void swap(QScopedPointer<T, Cleanup> &other) noexcept
    {
        qSwap(d, other.d);
    }

    typedef T *pointer;

protected:
    T *d;

private:
    Q_DISABLE_COPY(QScopedPointer)
};

template <class T, class Cleanup>
inline bool operator==(const QScopedPointer<T, Cleanup> &lhs, const QScopedPointer<T, Cleanup> &rhs) noexcept
{
    return lhs.data() == rhs.data();
}

template <class T, class Cleanup>
inline bool operator!=(const QScopedPointer<T, Cleanup> &lhs, const QScopedPointer<T, Cleanup> &rhs) noexcept
{
    return lhs.data() != rhs.data();
}

template <class T, class Cleanup>
inline bool operator==(const QScopedPointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
{
    return lhs.isNull();
}

template <class T, class Cleanup>
inline bool operator==(std::nullptr_t, const QScopedPointer<T, Cleanup> &rhs) noexcept
{
    return rhs.isNull();
}

template <class T, class Cleanup>
inline bool operator!=(const QScopedPointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
{
    return !lhs.isNull();
}

template <class T, class Cleanup>
inline bool operator!=(std::nullptr_t, const QScopedPointer<T, Cleanup> &rhs) noexcept
{
    return !rhs.isNull();
}

template <class T, class Cleanup>
inline void swap(QScopedPointer<T, Cleanup> &p1, QScopedPointer<T, Cleanup> &p2) noexcept
{ p1.swap(p2); }

template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> >
class QScopedArrayPointer : public QScopedPointer<T, Cleanup>
{
    template <typename Ptr>
    using if_same_type = typename std::enable_if<std::is_same<typename std::remove_cv<T>::type, Ptr>::value, bool>::type;
public:
    inline QScopedArrayPointer() : QScopedPointer<T, Cleanup>(nullptr) {}

    template <typename D, if_same_type<D> = true>
    explicit QScopedArrayPointer(D *p)
        : QScopedPointer<T, Cleanup>(p)
    {
    }

    inline T &operator[](int i)
    {
        return this->d[i];
    }

    inline const T &operator[](int i) const
    {
        return this->d[i];
    }

    void swap(QScopedArrayPointer &other) noexcept // prevent QScopedPointer <->QScopedArrayPointer swaps
    { QScopedPointer<T, Cleanup>::swap(other); }

private:
    explicit inline QScopedArrayPointer(void *) {
        // Enforce the same type.

        // If you get a compile error here, make sure you declare
        // QScopedArrayPointer with the same template type as you pass to the
        // constructor. See also the QScopedPointer documentation.

        // Storing a scalar array as a pointer to a different type is not
        // allowed and results in undefined behavior.
    }

    Q_DISABLE_COPY(QScopedArrayPointer)
};

template <typename T, typename Cleanup>
inline void swap(QScopedArrayPointer<T, Cleanup> &lhs, QScopedArrayPointer<T, Cleanup> &rhs) noexcept
{ lhs.swap(rhs); }

QT_END_NAMESPACE

#endif // QSCOPEDPOINTER_H

我们详细看看内部的实现。首先我们在第一部分已经比较简单的说明了QScopedPointer实现自动释放内存的实现原理,QScopedArrayPointer 是针对数组类型的连续内存空间的管理,从源码中可以看出它继承自QScopedPointer<T, Cleanup> 模板类,主要是增加了数组下标的访问方法,并重写了swap成员函数。

template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> >
class QScopedArrayPointer : public QScopedPointer<T, Cleanup>
{
	// ...
};

因此,我们对QScopedArrayPointer 不做过多追述,我们将剖析和解读的重点放在QScopedPointer上。
QScopedPointer 有一个成员变量 T* d 用于记录申请的内存空间地址。通过get()和data()可以返回d指针变量。
类中重载了一些常用的操作符,譬如 *取值操作符,-> 操作符等。

我们来看看几处比较复杂的语法。首先是关于模板类型的萃取。

template <typename Ptr>
    using if_same_type = typename std::enable_if<std::is_same<typename std::remove_cv<T>::type, Ptr>::value, bool>::type;

把嵌套语句拆解:
从最里层看

template <typename T >
typename std::remove_cv<T>::type;   // 移除最顶层 const 、最顶层 volatile 或两者,若存在。

template< class T >
typename std::remove_const<T>::type; // 移除最顶层 const

template< class T >
typename std::remove_volatile<T>::type; // 移除最顶层 volatile

详细解释见:remove_cv

然后我们看看std::is_same

template< class T, class U >
inline constexpr bool is_same_v = is_same<T, U>::value;

// 用于比较两种数据类型是否一致,若一致,value 为true,否则value为false;
template< bool B, class T = void >
typename std::enable_if<B,bool>::type;
// 若 B 为 true ,则 std::enable_if 拥有等同于 T 的公开成员 typedef type ;否则,无该成员 typedef 

template<class T>
struct enable_if<true, T> { typedef T type; };

明白了这几个模板类的意义之后,我们再回过头来完整的理解。
去除模板类型T的const和volatile属性,与Ptr类型比较是否为同一类型,如果类型一致,则

template<class T> struct enable_if<true, T> { typedef T type; };

否则:

template<bool B, class T = void> struct enable_if {};

更直接的理解为:
如果类型一致,cpp using if_same_type = T; , 否则编译报错。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt智能指针之QScopedPointer 的相关文章

随机推荐

  • TensorFlow 图片读取

    TensorFlow Version 2 0 0 image raw tf io read file img jpg image tf image decode image image raw channels None dtype tf
  • 2023高教社杯全国大学生数学建模竞赛C题思路模型代码

    2023高教社杯全国大学生数学建模竞赛C题思路模型代码 一 国赛常用的模型算法 1 蒙特卡罗算法 该算法又称随机性模拟算法 是通过计算机仿真来解决问题的算法 同时可以通过模拟可以来检验自己模型的正确性 比较好用的算法 2 数据拟合 参数估计
  • Zotero文献管理工具使用教程-2

    1 打开word 2 选择zotero标签 3 点击Add Edit Citation 4 选择要导入文献的格式后点击OK 这里以GB T 7714 1987为例 5 鼠标光标选中要插入的位置 前边一定要有文字 之后点击2所框选位置 6 点
  • 关于置信区间、置信度的理解

    关于置信区间和置信度的理解 在网上找了两个相关的观点感觉讲的很好 恍然大悟 简单概括 参数只有一个是固定的不会变 我们用局部估计整体 参数95 的置信度在区间A的意思是 正确 采样100次计算95 置信度的置信区间 有95次计算所得的区间包
  • 【leetcode 524. 通过删除字母匹配到字典里最长单词】双指针,在不同字符串中同向查找

    解题思路 依旧是双指针 不过双指针在不同字符串中同向查找 且在使用双指针前需要对被查找集合做排序 1 根据题目要求 先将dictionary的字符串按照字符串的长度从大到小排序 且字符串符合字典序 进行排序 目的是为了接下查找时 dicti
  • 使用streamstring获取字符串string的子串

    最近优化代码 特别是在C 中获取string的字串 代码经常会遇到 非常有用且重复的功能函数 对这个功能 我之前一直用substr来获取字串 功能也非常强大 最近发现一个非常好用的stringstream 用它来实现substr的部分功能
  • Springboot整合RabbitMQ详解

    RabbitMQ 文章目录 RabbitMQ RabbitMQ的特点 AMQP AMQP模型 消息确认 AMQP是一个可编程的协议 RabbitMQ安装 Windows10安装 步骤 Spring整合AMQP 官方中文文档 GitHup翻译
  • Windows Server 2012 R2 百度创建AD域

    Windows Server 2012 R2 创建AD域 前言 我们按照下图来创建第一个林中的第一个域 创建方法为先安装一台Windows服务器 然后将其升级为域控制器 然后创建第二台域控制器 一台成员服务器与一台加入域的Win8计算机 环
  • Linux终端查看文件指令

    可以用cat查看文件文本内容 还可以用more命令查看 两者不同的是 cat是直接将内容全部显示出来 more支持翻页 如果文件过多可以一页页的展示 翻页可以通过按空格实现
  • Mysql:核心的数据库操作

    Mysql核心点 对于每一位研发同学 Mysql都是必须掌握的技能啦 基本的Mysql的操作 还是得很好的掌握的 一 Mysql 学习一个技术 一定要先去官网学习 Mysql官网 二 基本的查询 1 创建表并插入数据 创建表 CREATE
  • 基于MindSpore的YOLOv3-DarkNet53网络实现

    基于MindSpore的YOLOv3 DarkNet53网络实现 网络模型介绍 1 backbone Darknet 53 YOLOv3使用Darknet 53提取特征 其借鉴了Darknet 19结构 不同于Darknet 19的是 Da
  • Flutter开发遇到的问题

    一 在AndroidStudio4 1中没有 New Flutter Project 菜单 那是由于你没有安装Flutter插件 需要在setting的插件管理中添加 Flutter 和 dart 插件 二 Flutter SDK 安装参考
  • 微信小程序input禁止空格输入

    用户输入的时候 可能会有输入空格的情况 所以我们要利用简单的正则实时去除空格 利用数据双向绑定的特性同步当前input的value值 下面是源码 wxml
  • 基于SpringBoot的螺蛳粉销售系统计算机毕业设计源码70795

    摘 要 随着供给侧结构性改革的稳步实施 互联网 这一新的国家发展的重要战略手段通过 双创 不但改变了传统的供需关系 还为经济发展带来了新动能 它已经成为产业发展的新引擎 螺顿粉产业就是在 互联网 背景下应运而生且蓬勃发展的 但是 在经济全球
  • 寻你的人生 寻你的选择

    无论如何选择 只要是自己的选择 就不存在对错与后悔 过去的我不会让现在的我满意 现在的我也不会让未来的我满意 当面对前路坎坷 我知道既然当初有胆量去选 那么就该有勇气把后果来承担 有毅力把梦想坚持并实现 我们人生中最大的懒惰 就是当我们明知
  • SonarQube8.7使用配置

    一 sonarQube版本 二 安装 三 配置说明 1 设置检测规则 2 启用pdf输出 一 sonarQube版本 本体 sonarqube 8 7 1 42226版本 插件 sonar findbugs plugin 4 0 3 jar
  • 生成Android的keystore密钥

    打开cmd 进入Jdk的 安装目录下的bin文件夹 输入命令 keytool genkey alias android keystore keyalg RSA validity 20000 keystore android keystore
  • /dev/sdb1 已经挂载或 /mnt/mountpoint3 忙解决办法

    dev sdb1 已经挂载或 mnt mountpoint3 忙解决办法 在挂载硬盘分区的时候 会出现mount dev sdd1 already mounted or data3 busy或者是在执行格式化分区的时候也会出现 dev hd
  • 操作系统重点

    1 1 选择题 1 考研真题 单项选择题 单道批处理系统的主要缺点是 A CPU利用率不高 2 考研真题 单项选择题 提高单机资源利用率的关键技术是 D 多道程序设计技术 3 考研真题 单项选择题 并发性是指若干事件在 发生 A C 同一时
  • Qt智能指针之QScopedPointer

    内存释放的问题是C 中比较头疼的问题 合理的使用智能指针能有效的帮助我们减少忘记释放内存 导致的内存泄露问题 本文以Qt中的QScopedPointer为例 通过讲解其用法 从源码深度剖析其实现方式 QScopedPointer的使用原理比