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 访问指针成员是安全的。
0 条评论