C++ 智能指针

江枫雨发布

c++ 类中的指针成员,当指针成员指向的对象被删除时,类的指针成员将指向一个无效的对象,变为悬垂指针。比如以下类定义:

#include <iostream>
using namespace std;

class HasPtr {
public:
    // 构造函数
    HasPtr(int *p) : ptr(p) {}

    // 获取指针
    inline int* getPtr() { return ptr;}

    // 获取指针对应的值
    inline int getVal() { return *ptr;}

    // 设置指针对应的值
    inline void setVal(int val) { *ptr = val; }

private:
    // 定义一个 int 指针成员
    int *ptr;
};

int main() {
    int *a = new int(100);
    HasPtr hp(a);
    hp.setVal(99);
    cout << hp.getVal() << endl;
    return 0;
}

输出

99

如果 main 函数中代码,在定义 hp 之后,delete 变量 a,即:

int main() {
    int *a = new int(100);
    HasPtr hp(a);

    // 释放 a 的地址
    delete a;

    hp.setVal(99);
    cout << hp.getVal() << endl;
    return 0;
}

则会导致 HasPtr 中的 ptr 指针成员变为悬垂指针。

当然有趣的是,实际上上述代码可能依然能正常执行,原因是执行 deleta a 之后,对 a 地址的使用是未定义的危险操作(可能报错,也可能正常执行)

要想保证 HasPtr 类中的 ptr 指针成员所指向的对象一定有效,需要定义智能指针。当然用户不能直接删除原指针,但可以读取。智能指针实现思路是对原指针的引用,引入引用计数(reference count),当对原指针新增引用时,计数 +1,解除引用时,计数 -1。当计数为 0 时,删除原指针。

首先,新增存储引用计数和原指针的类 S_Ptr:

class S_Ptr {
public:
    // 申明 HasPtr 为友元类,能访问 private 成员。
    friend class HasPtr;

    // 赋值 p 为原指针 ori,并设引用计数为 1.
    S_Ptr(int *ori) : p(ori), use(1) {}

    // 智能指针销毁时,删除原指针所指对象。
    ~S_Ptr() { delete p; cout << "Shared ptr deconstructor invoked. delete orgin pointer data." << endl; }

private:
    int *p;
    int use;
};

修改 HasPtr 类,指针操作通过共享指针 S_Ptr 来操作。

class HasPtr {
public:
    HasPtr(int *p) : ptr(new S_Ptr(p)) {}

    // 复制构造函数,引用计数 +1
    HasPtr(const HasPtr &ori) : ptr(ori.ptr) { ptr->use ++; }

    // 赋值操作符
    HasPtr& operator=(const HasPtr&);

    // 获取原指针
    inline int* get_ptr() { return ptr->p;}

    // 获取指针指向的值
    inline int get_val() { return *ptr->p;}

    // 设置指针指向的值
    inline void set_val(int val) { *ptr->p = val; }

    // 重载 << 操作符,为打印引用计数信息。
    friend ostream& operator<<(ostream& out, const HasPtr &hp);
    inline int get_use() const { return ptr->use; }

    // 析构函数引用计数 -1
    ~HasPtr() { -- ptr->use; };

private:
    // 指向智能指针,对原指针的操作都通过智能指针
    S_Ptr *ptr;
};

// 重载输出操作符时,输出引用计数信息。
ostream& operator<<(ostream& out, const HasPtr &hp) { out << "reference count: " << hp.get_use() << endl; return out; }

// 赋值操作符
HasPtr& HasPtr::operator=(const HasPtr & rhs) {
    // 右值的指针引用计数 +1
    ++rhs.ptr->use;

    // 左值的引用计数 -1,当引用计数为 0 时销毁共享指针。
    if (-- ptr->use == 0) {
        delete ptr;
    }

    // 复制指针
    ptr = rhs.ptr;
    return *this;
}

这里复杂点的是赋值操作符重载,需要先为右值引用计数+1,左值引用计数-1。
完整的测试程序:

#include <iostream>
using namespace std;

class HasPtr;
class S_Ptr {
public:
    // 申明 HasPtr 为友元类,能访问 private 成员。
    friend class HasPtr;

    // 赋值 p 为原指针 ori,并设引用计数为 1.
    S_Ptr(int *ori) : p(ori), use(1) {}

    // 智能指针销毁时,删除原指针所指对象。
    ~S_Ptr() { delete p; cout << "Shared ptr deconstructor invoked. delete orgin pointer data." << endl; }

private:
    int *p;
    int use;
};

class HasPtr {
public:
    HasPtr(int *p) : ptr(new S_Ptr(p)) {}

    // 复制构造函数,引用计数 +1
    HasPtr(const HasPtr &ori) : ptr(ori.ptr) { ptr->use ++; }

    // 赋值操作符
    HasPtr& operator=(const HasPtr&);

    // 获取原指针
    inline int* get_ptr() { return ptr->p;}

    // 获取指针指向的值
    inline int get_val() { return *ptr->p;}

    // 设置指针指向的值
    inline void set_val(int val) { *ptr->p = val; }

    // 重载 << 操作符
    friend ostream& operator<<(ostream& out, const HasPtr &hp);

    inline int get_use() const { return ptr->use; }

    // 析构函数引用计数 -1
    ~HasPtr() { -- ptr->use; };

private:
    // 指向智能指针
    S_Ptr *ptr;
};

// 重载输出操作符时,输出引用计数信息。
ostream& operator<<(ostream& out, const HasPtr &hp) { out << "reference count: " << hp.get_use() << endl; return out; }

// 赋值操作符
HasPtr& HasPtr::operator=(const HasPtr & rhs) {
    // 右值的指针引用计数 +1
    ++rhs.ptr->use;

    // 左值的引用计数 -1,当引用计数为 0 时销毁共享指针。
    if (-- ptr->use == 0) {
        delete ptr;
    }

    // 复制指针
    ptr = rhs.ptr;
    return *this;
}

int main() {
    int *a = new int(100);

    // 初始第一个 a 的引用 HasPtr 对象,引用计数变为 1
    HasPtr hp(a);
    cout << hp << endl;

    // 初始化第二个,引用计数变为 2
    HasPtr hp2 = hp;
    cout << hp << endl;

    // 释放第一个,引用计数变为 1
    hp = 0;
    cout << hp2 << endl;

    // 释放第二个,引用计数为0,自动调用共享指针的析构函数删除原指针数据
    hp2 = 0;

    return 0;

}

输出:

reference count: 1

reference count: 2

reference count: 1

Shared ptr deconstructor invoked. delete orgin pointer data.

可以看到原先 HasPtr 类中的整型指针成员 ptr,改为通过共享指针 S_Ptr 类管理后,在不直接删除原数据指针的前提下,直到引用计数为 0 后,共享指针才会执行析构函数,并切销毁原指针对象。所以通过 HasPtr 访问指针成员是安全的。

分类: c++

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注