c++中的智能指针auto_ptr解析

c++中的auto_ptr是一个类,却可以像指针一样去使用。使用auto_ptr需要包含头文件#include <memory>

例如:auto_ptr<string> ps(new string("hello"));可以像指针一样去使用它,可以这样cout << ps->size()<<endl;uto_ptr带来的好处是,程序员并不需要手动的去delete,这给容易忘记delete的程序员带来极大的好处,因为有时候delete真的是很容易忘记,而内存泄漏又是很难被发现的错误。java中采用了垃圾回收器的机制,使其很大程度上避免了内存泄漏的问题。

引例:

#include <iostream>
#include <memory>
using namespace std;

class A{
public:
	A(int x = 0) :m_x(x){
		cout << "A构造" << endl;
	}
	~A(){
		cout << "A析构" << endl;
	}
	void print()const{
		cout << m_x << endl;
	}
private:
	int m_x;
};

void test(){
	A * pa = new A(123);
	pa->print();
	delete pa;
}

int main(){
	cout << "main函数开始了" << endl;
	test();
	cout << "main函数结束" << endl;
}

上面的代码中,在test函数中如果我们忘记了delete,也就是将delete pa注释掉,则A类的析构函数是无法被调用的。在test函数中,A * pa是一个局部指针变量,函数结束pa是会被释放掉的,但是pa所指向的内存空间是无法被释放的。所以为了避免程序员忘记delete而造成内存泄漏,auto_ptr的思想就在于,能不能让pa所指向的内存空间伴随者pa的释放而被释放掉。试想一下,如果pa是一个类类型,且pa是分配在栈内存上,则pa被释放后,pa的析构函数是一定会被调用的。那么就在pa类的析构函数中释放pa所指向的堆内存空间,那么就不用手动delete了,pa就是auto_ptr。

所以如果我们把上面的代码中的A * pa = new A(123)注释掉,加上这样一句auto_ptr<A> pa(new A(123)),且把delete pa也注释掉,则会发现,A类的析构函数被调用了。

查看一下auto_ptr的部分源代码实现,首先着重看看auto_ptr的构造函数和析构函数。

template<class _Ty>

class auto_ptr

{ // wrap an object pointer to ensure destruction

public:

typedef auto_ptr<_Ty> _Myt;

typedef _Ty element_type;

explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()

: _Myptr(_Ptr)

{ // construct from object pointer

}

auto_ptr(_Myt& _Right) _THROW0()

: _Myptr(_Right.release())

{ // construct by assuming pointer from _Right auto_ptr

}

/*在这里代码有截断*/

~auto_ptr() _NOEXCEPT

{ // destroy the object

delete _Myptr;

}

/*在这里代码被截断*/

private:

_Ty *_Myptr;
// the wrapped object pointer

可以看出auto_ptr是用了类模板,可以接受任意引用类型的参数。以auto_ptr<string> ps(new string("hello"))这句话为例,执行这句话的时候,调用了auto_ptr的第一个构造函数,将new出来的地址给了_Ty *_Myptr,这里的_Ty已经被实例化成了string了。而且构造函数还加了explicit防止类型转换,也就是说这样写auto_ptr<string> ps = new string("hello")会报错。而且可以看到最后的析构函数将这个new出来的内存delete了,所以在使用auto_ptr的时候不用程序员手动释放了。

atuo_ptr的缺陷:

缺点1:auto_ptr缺乏拷贝语义。

c++中接触到的拷贝一般有三种,深拷贝,浅拷贝,转移拷贝。

a、浅拷贝:

就是逐字节拷贝,默认拷贝构造函数就是浅拷贝。直接将一个指针赋值给另一个指针,导致两个指针指向同一块内存。

b、深拷贝

一个指针赋值给另一个指针的同时,进行了内存的处理,两个指针有自己独立的内存空间。

c、转移拷贝

一个指针赋值给另一个指针之后,前一个指针(源指针被赋值为空)。

auto_ptr采用的就是转移语义的拷贝,会产生什么结果呢?我们把第一个例子的test函数改成这种样子。

void test(){
	/*pa本身会被释放,一种机制使得pa的释放导致堆内存的释放
	所以不再让一个普通指针来保存pa,用一个类来保存 */
	//A * pa = new A(123);
	auto_ptr<A> pa(new A(123));
	pa->print();
	//delete pa;
	/*智能指针的问题,缺乏拷贝语义,普通指针肯定没问题*/
	auto_ptr<A> pb = pa;//拷贝构造
	pb->print();
	/*段错误*/
	pa->print();
}

那么最后一句话pa->print()的执行将发生段错误,因为这个时候pa已经为空了,通过空指针去访问必然发生段错误。这里和普通指针的访问是不一样的,就是因为其拷贝方式特殊。

上面的三种拷贝语义,auto_ptr为什么没有使用浅拷贝语义或者深拷贝语义?如果用浅拷贝语义,那么在释放的时候必然出现对同一个指针的doulle free的问题,就是释放了两次。那么深拷贝呢?就是为拷贝对指针赋予独立的内存空间,这样做的问题是不符合指针拷贝的逻辑,因为一个指针拷贝给另外一个指针,两个指针还是指向的同一块内存,所以也没考虑深拷贝,最后就选择了一种转移拷贝的方式。

缺点2:不能管理对象数组

设计函数test2:

/*智能指针管理多个对象的问题*/
void test2(){
	auto_ptr<A> pa(new A[3]);//核心转存,解映射出错
	/*为什么智能指针不能管理对象数组*/
}

为什么auto_ptr不能管理对象数组呢?

一次分配多个对象我们必须要用delete[]去释放,不然会出错。最终的问题其实就是delete和delete[]的区别问题。

delete用于释放单个对象,获取的是对象的地址,并且调用free,而对象数组的分配有所不同,除了分配所需要大小的堆内存空间外,这段堆内存的开头还分配了4个字节的空间,用于存放分配对象的个数,在释放的时候必须把最开头的四个字节包含进去,delete[]在释放的时候,将得到的地址减4,这样取得了最开始的地址,这样的释放才是正确的释放,而delete的释放并没有减4的操作,所以无法正确的释放。

可以看到auto_ptr的析构函数中只有delete的释放,所以对象数组必然出错。

改进的auto_ptr使其能够管理对象数组。我们将对象数组特殊化处理,在这里面采用delete[]来释放,采用类模板的特化处理;

改进的版本,可以管理对象数组

#include <iostream>
using namespace std;
template<typename T>
class AutoPtr{
public:
	explicit AutoPtr(T*p = NULL) :m_p(p){}
	~AutoPtr(){
		if (m_p)
			delete m_p;
	}
	/*拷贝构造*/
	AutoPtr(AutoPtr<T>& that) :m_p(that.release()){}
	/*拷贝赋值*/
	AutoPtr& operator=(AutoPtr<T>& that){
		if (&that != this)
			reset(that.release());
		return *this;
	}
	T& operator* ()const{
		return *m_p;
	}
	T* operator->()const{
		return &**this;
		//return this->m_p;
	}
private:
	T* release(){
		T* p = m_p;
		m_p = NULL;
		return p;
	}
	/*重置m_p*/
	void reset(T* p){
		if (p != m_p){
			delete m_p;
			m_p = p;
		}
	}
	T * m_p;
};
/*特化的版本,全部重写的方式*/
template<typename T>
class AutoPtr<T[]>{
public:
	explicit AutoPtr(T*p = NULL) :m_p(p){}
	~AutoPtr(){
		if (m_p)
			delete[] m_p;
	}
	/*拷贝构造*/
	AutoPtr(AutoPtr<T>& that) :m_p(that.release()){}
	/*拷贝赋值*/
	AutoPtr& operator=(AutoPtr<T>& that){
		if (&that != this)
			reset(that.release());
		return *this;
	}
	T& operator* ()const{
		return *m_p;
	}
	T* operator->()const{
		return &**this;
		//return this->m_p;
	}
private:
	/*转移拷贝语义的实现函数1,把指针置为空,然后返回*/
	T* release(){
		T* p = m_p;
		m_p = NULL;
		return p;
	}
	/*重置m_p*/
	void reset(T* p){
		if (p != m_p){
			delete m_p;
			m_p = p;
		}
	}
	T * m_p;
};
class A{
public:
	A(int x = 0) :m_x(x){
		cout << "A构造" << endl;
	}
	~A(){
		cout << "A析构" << endl;
	}
	void print()const{
		cout << m_x << endl;
	}
private:
	int m_x;
};
void test(){
	AutoPtr<A> pa(new A(123));
	pa->print();
	(*pa).print();
	AutoPtr<A> pb = pa;
	pb->print();
	pa = pb;
	(*pa).print();
}
void test2(){
	AutoPtr<A[]> p(new A[3]);
}
int main(){
	test();
	test2();
}

上面代码的改进不仅是使auto_ptr具有原来的功能,还能管理对象数组,将对象数组做了特殊化处理。

值得说明的是在c++2011中出现的smart_ptr指针,真正做到了“智能”,其不仅可以管理对象数组,还具有指针的拷贝语义,在管理对象数组上smart_ptr采用了特化的处理,而在处理拷贝语义上采用了设置计数器的技术,这里不做详细说明。

 
时间: 2024-10-04 02:19:54

c++中的智能指针auto_ptr解析的相关文章

C++中的智能指针(auto_ptr)

实际上auto_ptr 只是C++标准库提供的一个类模板,它与传统的new/delete控制内存相比有一定优势,使用它不必每次都手动调用delete去释放内存.当然有利也有弊,也不是完全完美的. 本文从下面的8个方面来总结auto_ptr使用的大部分内容. 1. auto_ptr是什么? auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个这样拥有者(auto_ptr).当auto_ptr对象生命

C++中的智能指针(一):std::auto_ptr&lt;class T&gt; - &lt;memory&gt;

一:内存泄漏的问题 考虑如下的程序 void func() { ClassA *a = new ClassA; ... delete a; } 应该使用delete语句以保证new分配的空间一定会被释放.我几乎总会忘记delete语句,尤其函数要写return语句时,更容易忘记. 另外即使你加上了delete语句,你也无法完全避免空间无法释放的问题(这种问题的统一叫法:内存泄漏). void func() { ClassA *a = new ClassA; ... //产生异常 delete a

智能指针 shared_ptr 解析

最近正在进行<Effective C++>的第二遍阅读,书里面多个条款涉及到了shared_ptr智能指针,介绍的太分散,学习起来麻烦,写篇blog整理一下. LinJM   @HQU shared_ptr是一个智能指针.在C++ 11颁布之前,它包含在TR1(Technical Report 1)当中,现在囊括在C++11的标准库中. 智能指针 智能指针(Smart pointers)是存储"指向动态分配(在堆上)的对象的指针"的对象.也就是说,智能指针其实是个对象.不过

智能指针auto_ptr详解

概述:C++中有很多种智能指针,auto_ptr就是其中的一种,该智能指针主要是为了解决"因程序异常退出发生的内存泄漏"这类问题的. 我们先来看下面的问题代码 #include<iostream> #include<memory> #include<exception> using namespace std; //一般指针的处理方式 template<typename T> class OldClass { public: OldCla

C++:浅谈c++资源管理以及对[STL]智能指针auto_ptr源码分析,左值与右值

C++:浅谈c++资源管理以及对[STL]智能指针auto_ptr源码分析 by 小威威 1. 知识引入 在C++编程中,动态分配的内存在使用完毕之后一般都要delete(释放),否则就会造成内存泄漏,导致不必要的后果.虽然大多数初学者都会有这样的意识,但是有些却不以为意.我曾问我的同学关于动态内存的分配与释放,他的回答是:"只要保证new和delete成对出现就行了.如果在构造函数中new(动态分配内存),那么在析构函数中delete(释放)就可以避免内存泄漏了!" 事实果真如此么?

【C++智能指针 auto_ptr】

<More Effective C++>ITEM M9中提到了auto_ptr,说是当异常产生的时候,怎么释放为对象分配的堆内存,避免重复编写内存释放语句. PS:这里书里面提到函数退出问题,函数退出会清理栈内存,不管是怎么正常退出还是异常退出(仅有一种例外就是当你调用 longjmp 时.Longjmp 的这个缺点是 C++率先支持异常处理的主要原因).建立在此基础上我们才把对指针的删除操作封装到一个栈对象里面.这样函数退出(异常或是正常)就会调用对象的析构函数,达到我们自动清理所封装指针指

C++智能指针 auto_ptr

C++智能指针 auto_ptr auto_ptr 是一个轻量级的智能指针, 定义于 memory (非memory.h)中, 命名空间为 std. auto_ptr 适合用来管理生命周期比较短或者不会被远距离传递的动态对象, 最好是局限于某个函数内部或者是某个类的内部. 使用方法: std::auto_ptr<int> pt(new int(10)); pt.reset(new int(11)); 成员函数 3个重要的函数: (1) get 获得内部对象的指针, 由于已经重载了()方法, 因

模拟实现c++标准库和boost库中的智能指针

我们知道c++标准库中定义了智能指针auto_ptr,但是我们很少用它,因为虽然它能够自动回收动态开辟的内存,不需要程序员自己去维护动态开辟的内存,但是当用它去赋值或者是拷贝构造时有一个管理权转移的过程,这样我们就不能很方便的使用auto_ptr. 下面是简单的auto_ptr的实现,我们可以看到在复制和赋值时它将转移管理权. template<class T> class AutoPtr { public:      AutoPtr(T* ptr)       :_ptr(ptr)     

智能指针auto_ptr

前奏: Garbage Collection 技术一直颇受注目,并且在 Java 中已经发展成熟,成为内存管理的一大利器,但它在 C++ 语言中的发展却不顺利,C++ 为了追求运行速度,20 年来态度坚决地将其排除在标准之外. 为了稍许平复因为没有 Garbage Collection 而引发的 C++ 程序员的怨气,C++对 Smart Pointer 技术采取了不同的态度. 首先,了解一下智能指针, 该方法使用一个指针类来代表对资源的管理逻辑,并将指向资源的句柄(指针或引用)通过构造函数传递