重新读一下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
    10
    std::vector<std::shared_ptr<Widget>> widgets;
    class Widget
    {
    public:
    ...
    void process()
    {
    widgets.emplace_back(this);
    }
    }

    在这个代码中, 我们在emplace_back的时候, 利用this指针又创建了一个智能指针.
    解决措施是让Widgetstd::enable_shared_from_this来派生, 然后, 使用shared_from_this()来指涉自己:

    1
    2
    3
    4
    5
    6
    7
    8
    class 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
    2
    Widget w;
    w.process();

    为了避免这个问题, 比较好的做法是把Widget的构造函数隐藏起来, 只提供一个返回shared_ptr的工厂函数. 了比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class 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_ptrstd::weak_ptr作为实参, 则不会创建新的控制块.

上面的规则会导致一个后果: 当从同一个裸指针触发构造不止一个std::shared_ptr时, 被指涉的对象会有多个控制块, 这意味着这个对象会被析构多次! 例如下面的代码:

1
2
3
4
5
auto pw = new Widget;
...
std::shared_ptr<Widget> spw1(pw, loggingDel);
...
std::shared_ptr<Widget> spw2(pw, loggingDel);

解决这个问题的一种做法, 当必须将裸指针传递给std::shared_ptr时, 直接传递new运算发的结果, 而非传递一个裸指针. 这样可以降低用同一个裸指针创建第二个shared_ptr的机会.

1
2
3
std::shared_ptr<Widget> spw1(new Widget, loggingDel);
...
std::shared_ptr<Widget> spw2{spw1};

这样, 因为裸指针不在代码中出现, 当创建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
2
3
4
5
6
7
8
9
class Widget
{
public:
void process()
{
...
processWidgets.emplace_back(this);
}
}

上面的代码中, 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
2
3
4
5
6
7
8
9
class Widget: public std::enable_shared_from_this<Widget>
{
public:
void process()
{
...
processedWidgets.emplace_back(shared_from_this());
}
}

或者这样,使用Qt自己的东西,显得更Qt一些:

1
2
3
4
5
class Widget: public QEnableSharedFromThis<Widget>
{
public:
...
};

另外, 对于派生自std::enable_shared_from_this的类, 为了避免用户在std::shared_ptr指涉到该对象前就调用了引发shared_from_this的成员函数, 继承自std::enable_shared_from_this的类通常会将其构造函数声明为private的. 并且只允许用户通过调用返回std::shared_ptr的工厂函数来创建对象:

1
2
3
4
5
6
7
8
9
10
class Widget: public std::enable_shared_from_this<Widget>
{
public:
template<typename... Ts>
static std::shared_ptr<Widget> create(Ts&&... params);
void process();
...
private:
... // 构造函数
}