CPP中的智能指针
重新读一下Modern Effective C++
这本书。
CPP中的智能指针
unique_ptr和shared_ptr
- 尺寸:
- unique指针和普通指针相同大小, 除非定义了自定义析构函数.
- shared指针为两倍大小: 它还包括一个指向引用计数的指针. 它的析构函数指针不在类中.
析构函数
- unique_ptr的析构函数是类型的一部分, shared_ptr的析构函数是外部的东西
使用shared指针的注意点:
尽量使用
make_shared()
而不是使用构造函数. 避免多次使用裸指针构造shared_ptr时出现的问题: 尽量避免裸指针可见.千万不能将一个
shared_ptr
的指针传给一个shared_ptr
的容器.1
2
3
4
5
6
7
8
9
10std::vector<std::shared_ptr<Widget>> widgets;
class Widget
{
public:
...
void process()
{
widgets.emplace_back(this);
}
}在这个代码中, 我们在
emplace_back
的时候, 利用this
指针又创建了一个智能指针.
解决措施是让Widget
从std::enable_shared_from_this
来派生, 然后, 使用shared_from_this()
来指涉自己:1
2
3
4
5
6
7
8class Widget : public std::enable_shared_from_this<Widget>
{
public:
void process()
{
widgets.emplace_back(shared_from_this());
}
}shared_from_this
会查询当前对象的控制块, 并创建一个指涉到这个控制块的新的shared_ptr
. 这样, 就必须先有一个已经指向这个对象的shared_ptr
. 如果不存在,shared_from_this()
的行为就是未定义的.
例如,1
2Widget w;
w.process();为了避免这个问题, 比较好的做法是把
Widget
的构造函数隐藏起来, 只提供一个返回shared_ptr
的工厂函数. 了比如:1
2
3
4
5
6
7
8
9
10class Widget : public std::enable_shared_from_this<Widget>
{
public:
template<typename... Ts>
static std::shared_ptr<Widget> create(Ts&&... params);
process();
private:
Widget(...);
}
QScopedPointer, QSharedPointer和CPP标准智能指针的比较
- QSharedPointer
- QScopedPointer
- QWeakPointer
- QPointer, 它只能包含QObject派生的对象. 其他方面文档中写的很奇怪, 到底是类似于QSharedPointer还是QWeakPointer有点矛盾.
- QSharedDataPointer
- QExplicitlySharedDataPointer
Qt提供了更丰富的智能指针.
Qt也提供了std::enable_shared_from_this
的等价物: QEnableSharedFromThis
. 它的sharedFromThis()
行为描述的更准确:
If this (that is, the subclass instance invoking this method) is being managed by a QSharedPointer, returns a shared pointer instance pointing to this; otherwise returns a null QSharedPointer.
std::enable_share_from_this的概念
在Modern Effective CPP一书中的内容摘抄:
std::shared_ptr
有一个控制块, 来记录这个指针的引用计数, 弱技术, 其他数据等信息.- 一个对象的控制块由创建首个指涉到改对象的
std::shared_ptr
的函数来确定. 具体原则:std::make_shared
总会创建一个控制块
从具备专属所有权的指针( 即std::unique_ptr或std::auto_ptr ) 出发构造一个std::shared_ptr
时, 会创建一个控制块- 当
std::shared_ptr
构造函数使用裸指针作为实参来调用时, 会创建一个控制块. - 如果给
std::shared_ptr
的构造函数传递std::shared_ptr
或std::weak_ptr
作为实参, 则不会创建新的控制块.
上面的规则会导致一个后果: 当从同一个裸指针触发构造不止一个std::shared_ptr
时, 被指涉的对象会有多个控制块, 这意味着这个对象会被析构多次! 例如下面的代码:
1 | auto pw = new Widget; |
解决这个问题的一种做法, 当必须将裸指针传递给std::shared_ptr
时, 直接传递new
运算发的结果, 而非传递一个裸指针. 这样可以降低用同一个裸指针创建第二个shared_ptr
的机会.
1 | std::shared_ptr<Widget> spw1(new Widget, loggingDel); |
这样, 因为裸指针不在代码中出现, 当创建spw2
的时候, 就只能使用spw1
来创建, 即调用std::shared_ptr
的复制构造函数. 就不会出现上述问题:
在std::vector中保存shared_ptr时容易出现的问题
假设程序使用std::shared_ptr
来管理Widget
对象, 并使用一个vector
来追踪被处理的Widget:
1 | std::vector<std::shared_ptr<Widget>> processedWidgets; |
又假设Widget
有个成员函数来做处理:
1 | class Widget |
上面的代码中, process()
处理数据, 并在处理后, 将该Widget加入到链表中.
在这里, 我们将基于裸指针this
创建了一个std::shared_ptr
, 从而创建了一个新的控制块. 这在外面又套了一层.
很早以前我遇到过一个类似的问题, 也是在一个vector中保存智能指针, 在析构这个
vector
的时候出内存越界错误, 而改用裸指针就不会有问题. 回想起来, 应该就是这个原因.
对于这个问题, 可以使用std::enable_shared_from_this
来解决. 当你希望一个托管到std::shared_ptr
的类能够安全地由this
指针创建一个std::shared_ptr
时使用.
我们需要让Widget
继承自std::enable_shared_from_this
:
1 | class Widget: public std::enable_shared_from_this<Widget> |
或者这样,使用Qt自己的东西,显得更Qt一些:
1 | class Widget: public QEnableSharedFromThis<Widget> |
另外, 对于派生自std::enable_shared_from_this
的类, 为了避免用户在std::shared_ptr
指涉到该对象前就调用了引发shared_from_this
的成员函数, 继承自std::enable_shared_from_this
的类通常会将其构造函数声明为private的. 并且只允许用户通过调用返回std::shared_ptr
的工厂函数来创建对象:
1 | class Widget: public std::enable_shared_from_this<Widget> |