【EMC】第四章:智能指针

作者:wallace-lai
发布:2024-02-28
更新:2024-02-28

C++中的裸指针功能强大,但稍有不慎极易伤及自身。裸指针有以下这些常见的可怕之处:

(1)它的声明无法指示它到底是个对象还是个数组

int *p1 = new int;
int *p2 = new int[16];  // 对象与数组无法区分

(2)它的声明没有告诉你是否应该销毁它,即指针是否拥有所指之物(即指针所指对象的归属权是否属于该指针)

void f(int *p)
{
    // use p
    delete p    // 我应该销毁该指针所指对象吗?不知道
}

(3)如果你决定你应该销毁指针所指向对象,没人告诉你该用delete还是其它的析构机制

(4)接着(3),如果是用delete销毁,是delete单个对象还是delete数组,无法确定

void f(int *p)
{
    // use p
    // 使用 delete 还是其它的析构机制?不知道
    // 即使确定使用delete,那么是delete单个对象还是delete数组?不知道
}

(5)即使你能够正确销毁指针所指对象,但你很难确定你在所有执行路径上(包括异常路径)都恰好执行了一次销毁操作。少一次路径可能会导致泄露,而多销毁一次会导致未定义行为

(6)一般来讲没有办法告诉你指针是否变成了悬空指针

void f(int *p)
{
    // use p
    // ...
}

int *p = new int;

// use p

delete p;

f(p);   // p已经成为了悬空指针,但是f不知道

综上所述:裸指针很危险,极容易造成内存泄露和各种未定义行为,我们需要智能指针

条款18:对于独占资源使用unique_ptr

未完待续

条款22:当使用Pimpl手法时,请在实现文件中定义特殊成员函数

Pimpl手法

以下是一个常见的Widget类的实现。

// Widget.h
#include <string>
#include <vector>
#include "gadget.h"

class Widget {
public:
    Widget();
    // ..

private:
    std::string name;
    std::vector<double> data;
    Gadget g1, g2, g3;  // Gadget是用户自定义类型
};

因为Widget依赖了std::stringstd::vector以及Gadget,所以在编译Widget的时候必须将这些头文件包含进来。

(1)首先,这增加了Wdiget的编译耗时;

(2)其次,任何一个头文件的内容变动,都将导致类Widget的重新编译

为了解决编译耗时的问题,我们可以使用Pimpl手法,如下所示:

// Widget.h
class Widget {
public:
    Widget();
    // ..

private:
    struct Impl;
    Impl *pImpl;
};

Widget此时不再依赖std::stringstd::vector以及Gadget,所以Widget的使用者可以不需要因为头文件内容的改动而重新编译程序。类Widget的真正实现放在了Widget.cpp中,如下所示。

// Widget.cpp
#include "Widget.h"
#include "gadget.h"
#include <vector>
#include <string>

struct Widget::Impl {
    std::string name;
    std::vector<double> data;
    Gadget g1, g2, g3;
};

Widget::Widget() : pImpl(new Impl) {}

Widget::~Widget() { delete pImpl; }

然而上述代码是有问题的,可以使用智能指针优化一下。

// Widget.h
class Widget {
public:
    Widget();
    // ..

private:
    struct Impl;
    std::unique_ptr<Impl> pImpl;    // 使用智能指针而不是原始指针
};
// Widget.cpp
#include "Widget.h"
#include "gadget.h"
#include <vector>
#include <string>

struct Widget::Impl {
    std::string name;
    std::vector<double> data;
    Gadget g1, g2, g3;
};

Widget::Widget() : pImpl(std::make_unique<Impl>()) {}

此时,析构函数不见了,因为std::unique_ptr在自身析构的同时会自动销毁它所指向的对象。