C++内存泄漏
对于内存泄露,这是一个很重要的问题,我们分析了几个例子,来更全面的定义内存泄露。
例如如果我们分配了内存(new操作符),释放必须由某对象负责:它必须使用正确的delete操作符 删除这块内存,并且该任务只执行一次。 这里把删除内存的责任称为对象的所有权,
所以内存泄露是由于被分配的内存的所有权丢失了。怎么解决呢?方案就是:
当我们分配新内存时,必须立即把指向这块内存的指针赋值给某个智能指针,这样妈妈再也不用担心删除这块内存的问题了,该任务之后完全由智能指针负责,那关于智能指针我们需要注意些什么呢?
- 是否允许对智能指针进行复制
- 如果是,在智能指针的多份拷贝中,到底哪一个负责删除它们共同指向的对象?
- 智能指针是否表示指向一个对象数组或对象的指针
- 智能指针是否对应于一个常量指针和非常量指针
取决于这些问题的答案,我们有多种不同的智能指针,C++社区中有stl的auto_ptr和boost库的shared_ptr,后者是更值得使用的。
这里有两类智能指针,能够满足前面讨论的所有需要,有效的防止内存泄露。不同之处在于:引用计数指针可以被复制,而作用域指针不能被复制,但作用域指针效率更高,下面具体来讨论它:
1.引用计数指针
引用计数指针可以被复制,因此一个智能指针的几份拷贝可以指向同一个对象,这就产生了由哪份拷贝负责删除它们共同指向的对象这个问题。
答: 这组智能指针中最后消亡的那个将删除它所指向的对象(类似于最后一个屋子的人负责关灯的概念)。
其中这些指针共享一个计数器,记录有多少个智能指针引用同一个对象。这表明当有人创建一个指向目标对象的智能指针的一份拷贝是,计数加1,反之任何智能指针删除时,计数减1,同时我们也意识到该指针不是线程安全的并且创建计数指针的实参开销比较大。
//scpp_vector.h #ifndef _SCPP_VECTOR_ #define _SCPP_VECTOR_ #include <vector> #include "scpp_assert.h" namespace scpp { //wrapper around std::vector,在[]提供了临时的安全检查:重载[] <<运算符 template<typename T> class vector : public std::vector<T> { public: typedef unsigned size_type; //常用的构造函数 commonly use cons explicit vector(size_type n=0) : std::vector<T>(n) {} vector(size_type n,const T& value) : std::vector<T>(n,value) {} template <class InputIterator> vector(InputIterator first,InputIterator last) : std::vector<T>(first,last) {} //Note : we don't provide a copy-cons and assignment operator ? //使用scpp::vector提供更安全的下标访问实现,它可以捕捉越界访问错误 T& operator[] (size_type index) { SCPP_ASSERT( index < std::vector<T>::size() , "Index " << index << " must be less than " << std::vector<T>::size()); return std::vector<T>::operator[](index); }//? difference const T& operator[] (size_type index) const { SCPP_ASSERT( index < std::vector<T>::size() , "Index " << index << " must be less than " << std::vector<T>::size()); return std::vector<T>::operator[](index); } //允许此函数访问这个类的私有数据 //friend std::ostream& operator<< (std::ostream& os,const ) ? }; } //namespace scpp template<typename T> inline std::ostream& operator<< (std::ostream& os,const scpp::vector<T>& v) { for(unsigned i=0 ;i<v.size();i++) { os << v[i]; if( i+1 < v.size()) os << " "; } return os; } #endif
2.作用域指针
当我们并不打算复制智能指针,只是想保证被分配的资源被正确的回收,可采用一个更简单的方法-作用域指针
#ifndef _SCPP_SCOPEDPTR_H_
#define _SCPP_SCOPEDPTR_H_
#include "scpp_assert.h"
namespace scpp {
template<typename T>
class ScopedPtr {
public:
explicit ScopedPtr(T *p=NULL) :m_ptr(p) {}
ScopedPtr<T>& operator=(T *p) {
if(m_ptr !=p) {
delete m_ptr;
m_ptr=p;
}
return *this;
}
~ScopedPtr() {
delete m_ptr;
}
T* get() const {
return m_ptr;
}
T* operator->() const {
SCPP_ASSERT(m_ptr!=NULL,"Attempt to use operator -> on NULL pointer.");
return m_ptr;
}
T& operator* () const {
SCPP_ASSERT(m_ptr!=NULL,"Attempt to use operator * on NULL pointer.");
return *m_ptr;
}
//把对象的所有权释放给原始调用者
T* release() {
T *p=m_ptr;
m_ptr=NULL;
return p;
}
private:
T *m_ptr;
//copy is forbidden
ScopedPtr(const ScopedPtr<T>& rhs);
ScopedPtr<T>& operator=(const ScopedPtr<T>& rhs);
};
} //namespace scpp
#endif
//test_vector.cpp
#include "scpp_vector.h"
#include <iostream>
using namespace std;
int main() {
//usage-创建一个具有指定数量的vector:scpp::vector<int> v(n); 把n个vector元素都初始化为一个值:scpp::vector<int> v(n,val)
//方法3:scpp::vector<int> v; v.reserve(n),表示开始的vector是空的,对应的size()为0,
//并且开始添加元素时,在长度达到n之前,不会出现导致速度降低的容量增长现象
scpp::vector<int> vec;
for(int i=0;i< 3;i++){
vec.push_back(4*i);
}
cout << "The vector is : "<< vec << endl;
for(int i=0;i <= vec.size();i++) {
cout << "Value of vector at index " << i << " is " << vec[i] << endl;
}
return 0;
}
该类最重要的属性就是析构函数删除它指向的对象,并且作用域指针不能被复制(因为都声明为了私有,任何试图复制这种指针的代码都无法通过编译,这样就消除了对指向同一个对象的同一个智能指针的多份拷贝计数的需要)
3.用智能指针实行所有权
用法:
//不需要调用者负责删除这个对象,而是让函数返回一个智能指针
//更多的依赖编译器而不是程序员
RefCountPtr<A> res(new A(inputs));
ScopedPtr<A> res; //创建一个空作用域指针
void B::create(const Input& ins,ScopedPtr<A>& res);
//创建一个NULL值的作用域指针,并用下面的方法填充它,
//这种方法也不会使该函数创建的对象所有权出现错误
4.解引用NULL指针
对于解引用NULL指针,一般只需要在声明后或者分配内存后判断下指针是否为NULL指针即可,楼主的办法只是更为抽象而已,这里就不再描述。