C++98:auto_ptr
1.智能指针产生原因
众所周知,C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。然而,很多程序员在使用堆内存时都很头疼,因为一次不恰当的使用方法,很容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等。所以C++引入了智能指针,使用使用智能指针能更好的管理堆内存。
2.理解智能指针原理
1)智能指针是利用了一种叫做RAII技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
补充:RAII技术即资源分配及初始化,使用类来封装资源的分配和初始化,构造函数完成资源的分配和初始化,析构函数完成资源的清理,可以保证正确的初始化和资源释放。
2)智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放。智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。千万不要用一块非new分配的动态内存去初始化一个智能指针,因为自行释放非堆地址很有可能出现问题。
3)智能指针还有一个作用是把值语义转换成引用语义。
C++和Java有一处最大的区别在于语义不同,在Java里面,下列代码:
Student s1 = new Student ();
Student s2 = s1;
这里其实只生成了一个对象,s1和s2仅仅是共享对象的引用而已。但在C++中不是这样的,
Student s1;
Student s2 = s1;
这里却是就是生成了两个对象。
3.包含头文件
C++98版的auto_ptr和C++11版的shared_ptr、unique_ptr、weak_ptr,都包含在头文件<memory>中,隶属于std命名空间中。
4.auto_ptr
C++的auto_ptr所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理。
auto_ptr用法:
1)构造函数源码:
explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()
: _Myptr(_Ptr)
{ // construct from object pointer
}
将指针ptr交给auto_ptr对象托管。
2)拷贝构造源码:
typedef auto_ptr<_Ty> _Myt;
auto_ptr(_Myt& _Right) _THROW0()
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right auto_ptr
}
指针的托管权会发生转移。
3)赋值函数源码:
_Myt& operator=(_Myt& _Right) _THROW0()
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return (*this);
}
指针的托管权会发生转移。
4)析构函数源码:
~auto_ptr() _NOEXCEPT
{ // destroy the object
delete _Myptr;
}
释放指针_Myptr指向的空间。
C++中对一个空指针NULL执行delete操作是安全的。所以在auto_ptr的析构函数中无须判断它所拥有指针是否为空。
4)_Ty* get(),获得auto_ptr所拥有的指针。
_Ty *get() const _THROW0()
{ // return wrapped pointer
return (_Myptr);
}
5)_Ty* release(), 释放auto_ptr的所有权,并将所有用指针返回。
_Ty *release() _THROW0()
{ // return wrapped pointer and give up ownership
_Ty *_Tmp = _Myptr;
_Myptr = 0;
return (_Tmp);
}
6)void reset(T* ptr=0), 接收所有权,接收之前拥有其它指针的话,必须先释放其空间。
void reset(_Ty *_Ptr = 0)
{ // destroy designated object and store new pointer
if (_Ptr != _Myptr)
delete _Myptr;
_Myptr = _Ptr;
}
auto_ptr实现关键点:
1)利用特点“栈上对象在离开作用范围时会自动析构”。
2)对于动态分配的内存,其作用范围是程序员手动控制的,这给程序员带来了方便但也不可避免疏忽造成的内存泄漏,毕竟只有编译器是最可靠的。
3)auto_ptr通过在栈上构建一个对象a,对象a中wrap了动态分配内存的指针p,所有对指针p的操作都转为对对象a的操作。而在a的析构函数中会自动释放p的空间,而该析构函数是编译器自动调用的,无需程序员操心。
注意:
1)auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象,虽然编译可以通过,容易出现两个对象分别离开作用域时,被delete两次。
2)auto_ptr对象被拷贝或者被赋值后,已经失去了对原指针的所有权,此时,对这个auto_ptr的读取操作是不安全的。
auto_ptr<int> p1(new int(1));
auto_ptr<int> p2(p1);
cout << *p1 << endl;
//and
auto_ptr<int> p3=p1;
cout << *p1 << endl;
这种情况较为隐蔽的情形出现在将auto_ptr作为函数参数按值传递,因为在函数调用过程中在函数的作用域中会产生一个局部的临时auto_ptr对象来接收传入的 auto_ptr(拷贝构造),这样,传入的实参auto_ptr的对其指针的所有权转移到了临时auto_ptr对象上,临时auto_ptr在函数退出时析构,所以当函数调用结束,原实参所指向的对象已经被删除了。
void func(auto_ptr<int> ap)
{
cout << *ap << endl;
}
auto_ptr<int> ap(new int(1));
func(ap);
cout << *ap1 << endl;//错误,函数调用结束后,ap1已经不再拥有任何对象了
3)auto_ptr支持所拥有的指针类型之间的隐式类型转换。
class base{};
class derived: public base{};
//下列代码就可以通过,实现从auto_ptr<derived>到auto_ptr<base>的隐式转换,因为derived*可以转换成base*类型
auto_ptr<base> apbase = auto_ptr<derived>(new derived);
4)auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。
5)auto_ptr不能作为容器对象,C++的STL容器对于容器元素类型的要求是有值语义,即可以赋值和复制。auto_ptr在赋值和复制时都进行了特殊操作,所以auto_ptr对象不能作为STL容器元素。