内存释放的问题是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;
, 否则编译报错。