垂悬指针
定义:指针正常初始化,曾指向一个对象,该对象被销毁了,但是指针未制空,那么就成了悬空指针。
解决策略: 引入智能指针可以防止垂悬指针出现。一般是把指针封装到一个称之为智能指针类中,这个类中另外还封装了一个使用计数器,对指针的复制等操作将导致该计数器的值加1,对指针的delete操作则会减1,值为0时,指针为NULL。
哑指针
哑指针指传统的C/C++指针,它只是一个指向,除此以外它不会有其他任何动作,所有的细节必须程序员来处理,比如指针初始化,释放等等
野指针
定义: 指针指向了一块随机的空间,不受程序控制。访问一个已销毁或者访问受限的内存区域的指针,野指针不能判断是否为NULL来避免
产生的原因::
- 指针定义时未被初始化:指针在被定义的时候,如果程序不对其进行初始化的话,它会随机指向一个区域,因为任意指针变量(出了static修饰的指针)它的默认值都是随机的
- 指针被释放时没有置空:指针指向的内存空间在用free()和delete释放后,如果程序员没有对其进行置空或者其他赋值操作的话,就会成为一个野指针.
#include <stdio.h> #include <string.h> #include <malloc.h> #include <iostream> using namespace std; int main(void) { char *p = (char *) malloc(100); strcpy(p, "hello"); delete p; // p 所指的内存被释放,但是p所指的地址仍然不变,原来的内存变为“垃圾”内存(不可用内存 if ( p != NULL ) // 没有起到防错作用 { strcpy(p, "world"); } for(int i = 0; i < 5; i++) //i=5后为乱码 cout<<*(p+i)<<" "; cout<<endl; }
- 指针操作超越变量作用域:不要返回指向栈内存的指针或者引用,因为栈内存在函数结束的时候会被释放。
class A { public: void Func(void) { cout << "Func of class A" << endl; } }; class B { public: A * p; void Test(void) { A a; p = &a; // 注意 a 的生命期 ,只在这个函数Test中,而不是整个class B } void Test1(void) { p->Func(); // p 是“野指针” } };
注意:函数 Test1 在执行语句 p->Func()时,对象 a 已经消失,而 p 是指向 a 的,所以 p 就成了“野指针”
危害: 指针指向的内容已经无效了,而指针没有被置空,解引用一个非空的无效指针是一个未被定义的行为,也就是说不一定导致错误,野指针被定位到是哪里出现问题,在哪里指针就失效了,不好查找错误的原因。
规避方法:
- 初始化指针的时候将其置为nullptr,之后对其操作。
- 释放指针的时候将其置为nullptr。
智能指针(Smart Pointer)
引入原因
简单的说,智能指针是为了实现类似于Java中的垃圾回收机制。Java的垃圾回收机制使程序员从繁杂的内存管理任务中彻底的解脱出来,在申请使用一块内存区域之后,无需去关注应该何时何地释放内存,Java将会自动帮助回收。但是出于效率和其他原因(可能C++设计者不屑于这种傻瓜氏的编程方式),C++本身并没有这样的功能,其繁杂且易出错的内存管理也一直为广大程序员所诟病。
更进一步地说,智能指针的出现是为了满足管理类中指针成员的需要。包含指针成员的类需要特别注意复制控制和赋值操作,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。当类的实例在析构的时候,可能会导致垂悬指针问题。
指针的两种管理方法:
当类中有指针成员时,一般有两种方式来管理指针成员:
一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;
另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。它是指一种实现,能让指针在离开自己生命周期的时候自动销毁指向的内容(对象等),这往往用一个对象将指针包装起来来实现,例如标准库中的auto_ptr和boost中的智能指针都是智能指针的例子,但是缺点就是没有带引用参数。
智能指针的实现
智能指针的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。智能指针结合了栈的安全性和堆的灵活性,本质上将就是栈对象内部包装一个堆对象
每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。
实现引用计数有两种经典策略:一是引入辅助类,二是使用句柄类。
例如:
class TestPtr
{
public:
TestPtr(int *p):ptr(p){}
~TestPtr()
{
delete ptr;
}
// other operations
private:
int * ptr;
// other data
};
在程序中,类TestPtr对象的任何拷贝、赋值操作都会使多个TestPtr对象共享相同的指针。但在一个对象发生析构时,指针指向的对象将被释放,从而可能引起悬垂指针。
-
方法1:引用计数来解决,一个新的问题是引用计数放在哪里。显然,不能放在TestPtr类中,因为多个对象共享指针时无法同步更新引用计数。
class RefPtr { //所有成员都为私有 friend class TestPtr; int *ptr; size_t count; RefPtr (int *p): ptr(p), count(1) {} ~RefPtr () { delete ptr; } }; class TestPtr { public: TestPtr(int *p): ptr(new RefPtr(p)) { } TestPtr(const TestPtr& src): ptr(src.ptr) { ++ptr->count; } TestPtr& operator= (const TestPtr& rhs) { // self-assigning is also right ++rhs.ptr->count; if (--ptr->count == 0) delete ptr; ptr = rhs.ptr; return *this; } ~TestPtr() { if (--ptr->count == 0) delete ptr; } private: RefPtr *ptr; };
当希望每个TestPtr对象中的指针所指向的内容改变而不影响其它对象的指针所指向的内容时,可以在发生修改时,创建新的对象,并修改相应的引用计数。这种技术的一个实例就是写时拷贝(Copy-On-Write)。
缺点是每个含有指针的类的实现代码中都要自己控制引用计数,比较繁琐。特别是当有多个这类指针时,维护引用计数比较困难。 -
方法2:
为了避免上面方案中每个使用指针的类自己去控制引用计数,可以用一个类把指针封装起来。封装好后,这个类对象可以出现在用户类使用指针的任何地方,表现为一个指针的行为。我们可以像指针一样使用它,而不用担心普通成员指针所带来的问题,我们把这样的类叫句柄类。在封装句柄类时,需要申请一个动态分配的引用计数空间,指针与引用计数分开存储。
STL中的auto_ptr:
STL中auto_ptr只是众多可能的智能指针之一,auto_ptr所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理。
// 关于一个智能指针的定义
template<typename Type>
class auto_ptr
{
public:
auto_ptr(T *p =NULL) :Ptr(p)
{ }
~auto_ptr()
{
delete Ptr;
}
private:
Type *Ptr;
};
void ProcessAdoption(istream &data)
{
while (data) // 如果还有数据
{
auto_ptr<ALA> pa(readALADara(data));
pa->DealProcessAdoption(data);
}
return;
}
注意事项:
- auto_ptr不能共享所有权。
- auto_ptr不能指向数组
- auto_ptr不能作为容器的成员。
- 不能通过赋值操作来初始化auto_ptr
std::auto_ptr p(new int(42)); //OK
std::auto_ptr p = new int(42); //ERROR
这是因为auto_ptr 的构造函数被定义为了explicit - 不要把auto_ptr放入容器
智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。
事实上,智能指针能够做的还有很多事情,例如处理线程安全,提供写时复制,确保协议,并且提供远程交互服务。有能够为这些ESP (Extremely Smart Pointers)创建一般智能指针的方法,但是并没有涵盖进来。
智能指针的大部分使用是用于生存期控制,阶段控制。它们使用operator->和operator*来生成原始指针,这样智能指针看上去就像一个普通指针。
这样的一个类来自标准库:std::auto_ptr。它是为解决资源所有权问题设计的,但是缺少对引用数和数组的支持。并且,std::auto_ptr在被复制的时候会传输所有权。在大多数情况下,你需要更多的和/或者是不同的功能。这时就需要加入smart_ptr类。
smart_ptr 类
在Boost中的智能指针有:
-
scoped_ptr,用于处理单个对象的唯一所有权;与std::auto_ptr不同的是,scoped_ptr可以被复制
-
scoped_array,与scoped_ptr类似,但是用来处理数组的
-
shared_ptr,允许共享对象所有权
-
shared_array,允许共享数组所有权