C++ 03

auto_ptr

auto_ptr在C++11标准中已经被废弃。

先看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <memory>
using namespace std;
class A {
public:
A(int n):n_(n) { cout << "constructor:" << n_ << endl; }
~A(){ cout << "destructor:" << n_ << endl; }
void show() { cout << "A::show()" << endl; }
private:
int n_;
};
int main()
{
auto_ptr<A> p1(new A(6));
p1->show();
cout << "memory address of p1: " << p1.get() << endl;
auto_ptr<A> p2(p1);
p2->show();
cout << "memory address of p1 after copy : " << p1.get() << endl;
cout << "memory address of p2 after copy:" << p2.get() << endl;
return 0;
}

输出:

1
2
3
4
5
6
7
constructor:6
A::show()
memory address of p1: 0xe017d0
A::show()
memory address of p1 after copy : 0
memory address of p2 after copy:0xe017d0
destructor:6

可以看到,auto_ptr会在它自己被释放时自动析构它持有的对象。

它有一些已知的缺陷:

  • 无法接受数组分配对象

    1
    auto_ptr<int[]> p1(new int[4]); // 编译错误
  • 赋值给另一个auto_ptr时会转移所有权并将右值自动指针重置为null指针。基于这个原因,它无法在STL中使用。然后,它就被废弃了

C++ 11

unique_ptr

unique_ptr 是从 C++ 11 开始,定义在 中的智能指针(smart pointer)。它持有对对象的独有权,即两个 unique_ptr 不能指向一个对象,不能进行复制操作只能进行移动操作。它是auto_ptr的替代者。

它解决了auto_ptr的一些缺陷,支持数组,可以在STL中使用。

1
2
3
unique_ptr<int> p1(new int(5));
// Error: can't copy unique_ptr
unique_ptr<int> p2 = p1; // call to implicitly-deleted copy constructor of 'unique_ptr<int>'

但可以用move转移所有权。

1
2
3
4
5
6
7
unique_ptr<int> p1(new int(5));
unique_ptr<int> p2 = move(p1);
cout << "memory of p2: " << p2.get() << endl;
cout << "memory of p1: " << p1.get() << endl;
// Output:
// memory of p2: 0x7f952d405940
// memory of p1: 0x0

什么时候用

  • 当有可能出现异常安全时

    1
    2
    3
    4
    5
    {
    A* a = new A();
    a->do_something(); // 可能会发生异常
    delete a;
    }
  • 你不想要自己delete的时候

shared_ptr

shared_ptr也是从C++11开始引入的。它代表的是共享所有权,即可以多个shared_ptr共享同一块内存。

shared_ptr内部是利用引用计数来实现内存的自动管理,每当复制一个shared_ptr,引用计数会+1。当一个shared_ptr离开作用域时,引用计数会-1。当引用计数为0的时候,则delete内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
shared_ptr<int> p1 = make_shared<int>();
cout << p1.use_count() << endl;
{
auto p2 = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
}
cout << p1.use_count() << endl;
// Output:
// 1
// 2
// 2
// 1

什么时候使用

  • 当有可能多个对象同时管理同一个内存时。通常情况下,你都需要考虑你是否真的需要shared_ptr,还是unique_ptr已经满足你的需求。

weak_ptr

weak_ptr是shared_ptr的一个辅助,它并不真正持有对象。

1
2
auto sp = make_shared<int>(42);
weak_ptr<int> wp(sp);

可以通过它观测shared_ptr的引用计数。

1
2
3
4
5
{
shared_ptr<int> sp2 = sp;
cout << wp.use_count() << endl; // 2
}
cout << wp.use_count() << endl; // 1

可以通过lock函数将它转成shared_ptr可以拿到具体对象的值。

1
2
3
4
{
auto sp3 = wp.lock();
cout << *sp3 << endl; // 42
}

什么时候使用

  • 解决shared_ptr双向引用的问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include <iostream>
    #include <memory>
    using namespace std;
    struct B;
    struct A { shared_ptr<B> b; };
    struct B { shared_ptr<A> a; }; // 此处应改成 struct B { weak_ptr<A> a; };
    weak_ptr<A> wa;
    weak_ptr<B> wb;
    void observe()
    {
    cout << wa.use_count() << " " << wb.use_count() << endl;
    }
    int main()
    {
    {
    auto pa = make_shared<A>();
    auto pb = make_shared<B>();
    wa = pa; wb = pb;
    pa->b = pb; pb->a = pa;
    observe();
    }
    observe();
    }

    例如上面的例子,输出是

    1
    2
    2 2
    1 1

    引用计数始终不会变成0

    我们把B结构体中的改成weak_ptr再试一下。输出变成了

    1
    2
    1 2
    0 0

Qt

QPointer

严格意义上讲,QPointer并不算是智能指针。智能指针是为了自动释放内存资源而设计的,而QPointer只是它持有的对象被销毁后,它将自动置空。且它只使用于QObject的实例。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <QtCore>
class FA : public QObject {
Q_OBJECT
public:
FA(QObject *parent) : QObject(parent) {}
};
class FB : public QObject {
Q_OBJECT
public:
FB(QObject *parent) : QObject(parent) {}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
FA *fa = new FA(nullptr);
FB *fb = new FB(fa);
qDebug() << fb;
delete fa;
qDebug() << (fb == nullptr);
return a.exec();
}
#include "main.moc"

先输出一个具体的内存地址,再输出false。

如果换成QPointer呢?

1
QPointer<FB> fb = new FB(fa);

输出的就是true了,因为它会自动置空。

当然,你可以用QPointer的isNull()方法更优雅的判空。

QPointer曾计划被废弃,但为了支持老代码被保留了下来。

QScopedPointer

QScopedPointer保证当当前范围消失时指向的对象将被删除。这个智能指针只能在本作用域里使用,不希望被转让,因为它的拷贝构造和赋值操作都是私有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <QtCore>
struct FA {
FA() {}
~FA() { qDebug() << "destructor"; }
void foo() { qDebug() << "foo"; }
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
{
QScopedPointer<FA> fa(new FA);
fa->foo();
}
return a.exec();
}

输出:

1
2
foo
destructor

QScopedArrayPointer

QScopedArrayPointer继承自QScopedPointer,唯一的不同是它专门处理数组,离开作用域后,它会使用delete []来删除对象。

1
2
3
4
5
6
7
void foo()
{
QScopedArrayPointer<int> i(new int[10]);
i[2] = 42;
...
return; // our integer array is now deleted using delete[]
}

QSharedPointer

它与C++11中的shared_ptr类似,可以多个QSharedPointer共享一块内存,线程安全,也支持自定义deleter,同样使用引用计数的方式来实现内存的自动管理。

我们可以使用new的方式来创建它:

1
QSharedPointer<FA> fa(new FA);

也可以用它的静态函数create(), create的参数是对象的构造函数的参数:

1
auto fa = QSharedPointer<FA>::create();

可以通过data()拿到裸指针。

1
auto naked_ptr = fa.data();

也可以自定义删除器。附一个完整例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <QtCore>
struct FA {
FA() {}
~FA() { qDebug() << "destructor"; }
void foo() { qDebug() << "foo"; }
};
static void my_deleter(FA *fa)
{
qDebug() << "my deleter";
delete fa;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
{
QSharedPointer<FA> fa(new FA(), my_deleter);
fa->foo();
}
return a.exec();
}

输出:

1
2
3
foo
my deleter
destructor

可以安全的放到容器中:

1
2
3
4
5
6
7
8
9
typedef QSharedPointer<FA> FAPtr;
typedef QList<FAPtr> FAPtrLst;

{
FAPtrLst lst;
lst.append(QSharedPointer<FA>::create());
lst.append(QSharedPointer<FA>::create());
lst.append(QSharedPointer<FA>::create());
}

QWeakPointer

与C++11中的weak_ptr/shared_ptr类似,QWeakPointer也是QSharedPointer的辅助。它是一个弱引用,不会改变引用计数。

同样地,可以用它来避免双向引用的问题。(此处不再举例)

QSharedDataPointer

这是为配合 QSharedData 实现隐式共享(写时复制 copy-on-write))而提供的便利工具。

隐式共享的目的是最大化资源使用和最小化拷贝代价。

Qt中众多的类都使用了隐式共享技术,比如QPixmap、QByteArray、QString。

QExplicitlySharedDataPointer

这是为配合 QSharedData 实现显式共享而提供的便利工具。

QExplicitlySharedDataPointer 和 QSharedDataPointer 非常类似,但是它禁用了写时复制功能。

Reference