C++智能指针(1):auto_ptr
分析
C++ 中经常会出现因为没有 delete 指针而造成的内存泄漏,例如有一个 Object 类
class Object {
public:
Object() { std::cout << "Construct" << std::endl; }
Object(const Object &other) { std::cout << "Copy" << std::endl; }
Object(Object &&other) noexcept { std::cout << "Move" << std::endl; }
~Object() { std::cout << "Destruct" << std::endl; }
void Print() { std::cout << "Print" << std::endl; }
};
创建一个指向 Object 类型的指针
int main() {
Object *o = new Object();
o->Print();
return 0;
}
/*
output:
Construct
Print
*/
我们没有进行delete o的操作,导致o没有被正确地析构,造成了内存泄漏。作为对比,创建一个Obj类型的对象
int main() {
Object *o1 = new Object();
o1->Print();
Object o2 = Object();
o2.Print();
return 0;
}
/*
output:
Construct
Print
Construct
Print
Destruct
*/
产生这样的结果是因为对象创建在栈(stack)上,编译器会自动进行对象的创建和销毁,而指针是创建在堆(heap)上,需要手动进行创建和销毁。为了规避这样的问题,我们可以封装一个智能指针类,用类来管理指针,防止造成内存泄漏,并且尽可能的模仿指针的用法。
实现
根据auto_ptr的源码,能够大致实现 AutoPointer 类
template<typename T>
class AutoPointer {
public:
explicit AutoPointer(T *t);
~AutoPointer();
T &operator*();
T *operator->();
T *release();
void reset(T *p);
AutoPointer(AutoPointer<T> &other);
AutoPointer<T> &operator=(AutoPointer<T> const &other);
private:
T *pointer;
};
template<typename T>
AutoPointer<T>::AutoPointer(T *t) {
std::cout << "AutoPointer " << this << " constructor called." << std::endl;
this->pointer = t;
}
template<typename T>
AutoPointer<T>::~AutoPointer() {
std::cout << "AutoPointer " << this << " destructor called." << std::endl;
delete this->pointer;
}
template<typename T>
T &AutoPointer<T>::operator*() {
return *this->pointer;
}
template<typename T>
T *AutoPointer<T>::operator->() {
return this->pointer;
}
template<typename T>
T *AutoPointer<T>::release() {
T *new_pointer = this->pointer;
this->pointer = nullptr;
return new_pointer;
}
template<typename T>
void AutoPointer<T>::reset(T *p) {
if (this->pointer != p) {
delete this->pointer;
this->pointer = p;
}
}
template<typename T>
AutoPointer<T>::AutoPointer(AutoPointer<T> &other) {
std::cout << "AutoPointer " << this << " copy constructor called." << std::endl;
this->pointer = other.release();
}
template<typename T>
AutoPointer<T> &AutoPointer<T>::operator=(AutoPointer<T> const &other) {
std::cout << "AutoPointer " << this << " assignment operator called." << std::endl;
if (this->pointer != other.pointer)
this->reset(other.release());
return *this;
}
- 构造函数直接将 AutoPointer 类的 pointer 指针指向传入的参数指针所指向的地址
- 拷贝构造函数先对参数对象的指针进行 release 操作,也就是将参数对象的私有成员 pointer 指针置为 nullptr 并返回其原本指向的地址,然后将自身的 pointer 指向这个地址
- 赋值操作符先判断传入的参数是否是当前的 AutoPointer 类对象本身,如果是的话直接返回 this 指针,否则先对参数对象的指针进行 release 操作,并 delete 掉当前对象的 pointer,再将 pointer 指向参数对象的 pointer 原本指向的地址,这样的实现有效地规避了迷途指针(也称悬空指针或野指针)。
测试
创建单个 AutoPointer 类对象时能够正常使用。
int main() {
Object *o = new Object();
AutoPointer<Object> a1(o);
(*a1).Print();
a1->Print();
return 0;
}
/*
output:
Construct
AutoPointer 0x7fe680c02ab0 constructor called.
Print
Print
AutoPointer 0x7fe680c02ab0 destructor called.
Destruct
*/
创建两个 AutoPointer 类对象时如果使用同一个 Object 指针进行初始化,那么在程序退出时 Object 对象会被两个 AutoPointer 类对象各析构一次,也就是说同一块地址会被 delete 两次,造成运行时报错。
int main() {
Object *o = new Object();
AutoPointer<Object> a1(o);
AutoPointer<Object> a2(o);
return 0;
}
/*
output:
Construct
AutoPointer 0x7ffee3fa0178 constructor called.
AutoPointer 0x7ffee3fa0170 constructor called.
AutoPointer 0x7ffee3fa0170 destructor called.
Destruct
AutoPointer 0x7ffee3fa0178 destructor called.
Destruct
cpp(9015,0x1197a25c0) malloc: *** error for object 0x7fe9dec02b40: pointer being freed was not allocated
cpp(9015,0x1197a25c0) malloc: *** set a breakpoint in malloc_error_break to debug
*/
使用拷贝构造函数将一个 AutoPointer 类对象 a1 拷贝给另一个 AutoPointer 类对象 a2 时,Object 指针 o 原本是属于 a1 的,在 a2 调用拷贝构造函数之后,a1 的 pointer 变成了空指针,而 s2 拥有了指针 o,造成了所有权转移。
int main() {
Object *o = new Object();
AutoPointer<Object> a1(o);
AutoPointer<Object> a2(a1);
return 0;
}
/*
output:
Construct
AutoPointer 0x7fd15bc02ab0 constructor called.
AutoPointer 0x7fd15bc02ab0 copy constructor called.
AutoPointer 0x7fd15bc02ab0 destructor called.
Destruct
AutoPointer 0x0 destructor called.
*/
使用赋值操作符也会有所有权转移的问题。
int main() {
Object *o = new Object();
AutoPointer<Object> a1(o);
AutoPointer<Object> a2 = a1;
return 0;
}
/*
output:
Construct
AutoPointer 0x7ff5d5402ab0 constructor called.
AutoPointer 0x7ff5d5402ab0 copy constructor called.
AutoPointer 0x7ff5d5402ab0 destructor called.
Destruct
AutoPointer 0x0 destructor called.
*/
总结
AutoPointer 有效地解决了野指针问题,但又会引入一些其他的问题,例如
所有权转移
- 将 AutoPointer 作为参数进行拷贝构造或赋值操作时造成所有权转移
内存泄漏
- 在析构函数中使用了delete进行指针的销毁,但如果以数组指针进行初始化
AutoPointer<int> s1(new int[10])
会因为没有销毁数组的其它元素而造成内存泄漏
- 在析构函数中使用了delete进行指针的销毁,但如果以数组指针进行初始化
auto_ptr源码
template<class _Tp>
class _LIBCPP_TEMPLATE_VIS auto_ptr
{
private:
_Tp* __ptr_;
public:
typedef _Tp element_type;
_LIBCPP_INLINE_VISIBILITY explicit auto_ptr(_Tp* __p = 0) throw() : __ptr_(__p) {}
_LIBCPP_INLINE_VISIBILITY auto_ptr(auto_ptr& __p) throw() : __ptr_(__p.release()) {}
template<class _Up> _LIBCPP_INLINE_VISIBILITY auto_ptr(auto_ptr<_Up>& __p) throw()
: __ptr_(__p.release()) {}
_LIBCPP_INLINE_VISIBILITY auto_ptr& operator=(auto_ptr& __p) throw()
{reset(__p.release()); return *this;}
template<class _Up> _LIBCPP_INLINE_VISIBILITY auto_ptr& operator=(auto_ptr<_Up>& __p) throw()
{reset(__p.release()); return *this;}
_LIBCPP_INLINE_VISIBILITY auto_ptr& operator=(auto_ptr_ref<_Tp> __p) throw()
{reset(__p.__ptr_); return *this;}
_LIBCPP_INLINE_VISIBILITY ~auto_ptr() throw() {delete __ptr_;}
_LIBCPP_INLINE_VISIBILITY _Tp& operator*() const throw()
{return *__ptr_;}
_LIBCPP_INLINE_VISIBILITY _Tp* operator->() const throw() {return __ptr_;}
_LIBCPP_INLINE_VISIBILITY _Tp* get() const throw() {return __ptr_;}
_LIBCPP_INLINE_VISIBILITY _Tp* release() throw()
{
_Tp* __t = __ptr_;
__ptr_ = 0;
return __t;
}
_LIBCPP_INLINE_VISIBILITY void reset(_Tp* __p = 0) throw()
{
if (__ptr_ != __p)
delete __ptr_;
__ptr_ = __p;
}
_LIBCPP_INLINE_VISIBILITY auto_ptr(auto_ptr_ref<_Tp> __p) throw() : __ptr_(__p.__ptr_) {}
template<class _Up> _LIBCPP_INLINE_VISIBILITY operator auto_ptr_ref<_Up>() throw()
{auto_ptr_ref<_Up> __t; __t.__ptr_ = release(); return __t;}
template<class _Up> _LIBCPP_INLINE_VISIBILITY operator auto_ptr<_Up>() throw()
{return auto_ptr<_Up>(release());}
};