1. 概述
本篇对值型的智能指针进行仿真,这类指针所有权是不能共享的。
仿真指针主要有。
1) 旧式stl智能指针:auto_ptr。
2) Boost智能指针:scoped_ptr。
3) C++ 11 tr1的智能指针:unique_ptr。
由于不能进行所有权共享,故这类指针对复制和赋值运算都做了限制处理。在使用过程中涉及到所有权的时候要小心谨慎,防止踩雷。
2. auto_ptr仿真
2.1. auto_ptr相关问题
仿真auto_ptr就有几个尖锐问题需要提出来。
1) auto_ptr名称为何不好,以至于出来如此多的智能指针要替代它?
2) auto_ptr为何不容于容器?
3) auto_ptr使用中有哪些坑?
2.2. auto_ptr仿真源码
对应实现类MyAutoPtr.h。
#pragma once
template<class _Ty>
class CMyAutoPtr
{
public:
typedef CMyAutoPtr<_Ty> _Myt;
typedef _Ty element_type;
/// @brief 构造函数传入持有指针
explicit CMyAutoPtr(_Ty *_Ptr = nullptr)
: _Myptr(_Ptr)
{}
/// @brief 析构函数删除持有指针
~CMyAutoPtr()
{
delete _Myptr;
_Myptr = nullptr;
}
/*! @brief 拷贝构造函数 为了进行指针所有权转移动作,故需要传递非const引用进来 */
CMyAutoPtr(_Myt& _Right)
: _Myptr(_Right.release())
{}
/*! @brief 赋值函数 为了进行指针所有权转移动作,故需要传递非const引用进来 */
_Myt& operator=(_Myt& _Right)
{
reset(_Right.release());
return (*this);
}
/// @brief 仿真指针操作
_Ty& operator*() const {return (*get());}
_Ty* operator->() const {return (get());}
/// @brief 获取持有指针
_Ty* get() const {return (_Myptr);}
/// @brief 释放持有指针,并转移出所持有指针
_Ty* release()
{
_Ty *_Tmp = _Myptr;
_Myptr = nullptr;
return (_Tmp);
}
/// @brief 重置持有指针
void reset(_Ty *_Ptr = nullptr)
{
if (_Ptr != _Myptr)
{
delete _Myptr;
_Myptr = nullptr;
}
_Myptr = _Ptr;
}
private:
_Ty *_Myptr;///< 持有指针 };
程序调用主函数。
#include <iostream>
#include "MyAutoPtr.h"
class CTest
{
public:
CTest():m_nTest(0){std::cout<<"CTest::CTest()"<<std::endl;}
~CTest(){std::cout<<"CTest::~CTest()"<<std::endl;}
void print() {std::cout<<"CTest::print()"<<m_nTest<<std::endl;}
private:
int m_nTest;
};
int _tmain(int argc, _TCHAR* argv[])
{
CMyAutoPtr<CTest> pa(new CTest);
return 0;
}
运行结果:
CTest::CTest()
CTest::~CTest()
2.3. auto_ptr问题剖析
对于auto_ptr的问题做下剖析。
1) auto_ptr为何不能放入容器
我们将智能指针放入容器中编译一下。
int _tmain(int argc, _TCHAR* argv[])
{
CMyAutoPtr<CTest> pa(new CTest);
std::vector<CMyAutoPtr<CTest>> vTest;
vTest.push_back(pa);
return 0;
}
编译报错如下。
error C2558: class“CMyAutoPtr<_Ty>”: 没有可用的复制构造函数或复制构造函数声明为“explicit”
跟踪地方报错位置在这里。
这里容器需要调用拷贝构造函数来创建对象,对应到我们程序的具体实现,就是容器需要我们提供一个这样的拷贝构造函数。
CMyAutoPtr(const _Myt& _Right)
显然我们提供是这样的拷贝构造函数。
故auto_ptr不能放入到容器中。
注:关于构造函数相关内容参加《Effective C++》条款05:了解C++默默编写并调用哪些函数。
2) auto_ptr使用中有哪些坑?
坑1 所有智能指针都有的坑
一个原生指针不能被多个智能指针所持有。
int _tmain(int argc, _TCHAR* argv[])
{
CTest* pTest = new CTest;
CMyAutoPtr<CTest> pa(pTest);
CMyAutoPtr<CTest> pb(pTest);
return 0;
}
程序运行会爆机,由于同一块内存被释放多次引起。等同于如下代码。
int _tmain(int argc, _TCHAR* argv[])
{
CTest* pTest = new CTest;
delete pTest;
delete pTest;
return 0;
}
建议初始化写法:
CMyAutoPtr<CTest> pa(new CTest);
坑2 所有权的隐晦转移问题
int _tmain(int argc, _TCHAR* argv[])
{
CMyAutoPtr<CTest> pa(new CTest);
CMyAutoPtr<CTest> pb = pa;
pa->print();
return 0;
}
程序会爆机
原因:指针所有权已转移,pa此时所持有的指针为空。
3. boost::scoped_ptr仿真
boost::scoped_ptr类似与auto_ptr类似。改进点在于,显式禁止复制或赋值操作,减少了隐晦的所有权转移问题。
具体实现方法就是禁止指针的复制和赋值。
3.1. boost::noncopyable仿真
对应实现Uncopyable.h。
#pragma once
class Uncopyable
{
protected:
Uncopyable() {}
~Uncopyable() {}
private:
/// @brief 明确拒绝复制和赋值操作
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
3.2. scoped_ptr仿真源码
MyScopedPtr.h
#pragma once
#include "Uncopyable.h"
template<class _Ty>
class CMyScopedPtr : private Uncopyable
{
public:
typedef CMyScopedPtr<_Ty> _Myt;
typedef _Ty element_type;
/// @brief 构造函数传入持有指针
explicit CMyScopedPtr(_Ty *_Ptr = nullptr)
: _Myptr(_Ptr)
{}
/// @brief 析构函数删除持有指针
~CMyScopedPtr()
{
delete _Myptr;
_Myptr = nullptr;
}
/// @brief 仿真指针操作
_Ty& operator*() const {return (*get());}
_Ty* operator->() const {return (get());}
/// @brief 获取持有指针
_Ty* get() const {return (_Myptr);}
/// @brief 释放持有指针,并转移出所持有指针
_Ty* release()
{
_Ty *_Tmp = _Myptr;
_Myptr = nullptr;
return (_Tmp);
}
/// @brief 重置持有指针
void reset(_Ty *_Ptr = nullptr)
{
if (_Ptr != _Myptr)
{
delete _Myptr;
_Myptr = nullptr;
}
_Myptr = _Ptr;
}
private:
_Ty *_Myptr;///< 持有指针 };
程序调用
#include <iostream>
#include "MyScopedPtr.h"
class CTest
{
public:
CTest():m_nTest(0){std::cout<<"CTest::CTest()"<<std::endl;}
~CTest(){std::cout<<"CTest::~CTest()"<<std::endl;}
void print() {std::cout<<"CTest::print()"<<m_nTest<<std::endl;}
private:
int m_nTest;
};
int _tmain(int argc, _TCHAR* argv[])
{
CMyScopedPtr<CTest> pa(new CTest);
return 0;
}
3.3. scoped_ptr改进检测
检测下所有权隐晦转移问题。
进行拷贝操作失败。
CMyScopedPtr<CTest> pb = pa;
CMyScopedPtr<CTest> pb; pb = pa;
4. std::unique_ptr仿真
boost::shared_ptr虽然做出了一些改进,但仍有一些不足。
1) 不能做为返回类型。
2) 不能放入容器中。
C++11的右值引用与移动语义使这一行为成为可能,std::unique_ptr就是这样的存在,我们仿真一下其实现,看底层是如何运作的。
4.1. unique_ptr仿真源码
MyUniquePtr.h
#pragma once
#include "Uncopyable.h"
template<class _Ty>
class CMyUniquePtr : private Uncopyable
{
public:
typedef CMyUniquePtr<_Ty> _Myt;
typedef _Ty element_type;
/// @brief 构造函数传入持有指针
explicit CMyUniquePtr(_Ty *_Ptr = nullptr)
: _Myptr(_Ptr)
{}
/// @brief 析构函数删除持有指针
~CMyUniquePtr()
{
delete _Myptr;
_Myptr = nullptr;
}
/// @brief 移动构造函数
CMyUniquePtr(_Myt&& _Right)
: _Myptr(_Right.release())
{}
/// @brief 移动赋值操作符
_Myt& operator=(_Myt&& _Right)
{
reset(_Right.release());
return (*this);
}
/// @brief 仿真指针操作
_Ty& operator*() const {return (*get());}
_Ty* operator->() const {return (get());}
/// @brief 获取持有指针
_Ty* get() const {return (_Myptr);}
/// @brief 释放持有指针,并转移出所持有指针
_Ty* release()
{
_Ty *_Tmp = _Myptr;
_Myptr = nullptr;
return (_Tmp);
}
/// @brief 重置持有指针
void reset(_Ty *_Ptr = nullptr)
{
if (_Ptr != _Myptr)
{
delete _Myptr;
_Myptr = nullptr;
}
_Myptr = _Ptr;
}
private:
_Ty *_Myptr;///< 持有指针 };
调用程序
#include <iostream>
#include <vector>
#include "MyUniquePtr.h"
class CTest
{
public:
CTest():m_nTest(0){std::cout<<"CTest::CTest()"<<std::endl;}
~CTest(){std::cout<<"CTest::~CTest()"<<std::endl;}
void print() {std::cout<<"CTest::print()"<<m_nTest<<std::endl;}
private:
int m_nTest;
};
CMyUniquePtr<CTest> Test()
{
CMyUniquePtr<CTest> test(new CTest());
return test;
}
int _tmain(int argc, _TCHAR* argv[])
{
CMyUniquePtr<CTest> test = Test();///< 支持返回值
std::vector<CMyUniquePtr<CTest>> vTest;
vTest.push_back(std::move(test));///< 直接进也不行,需要进行move一下
return 0;
}
程序编译运行都正常。
4.2. 使用时注意问题
如上述示例代码中所示test放入容器后,不可再对test进行操作,此时其已不再持有原生指针。