智能指针仿真-002-值型智能指针

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: classCMyAutoPtr<_Ty>”: 没有可用的复制构造函数或复制构造函数声明为“explicit

跟踪地方报错位置在这里。

这里容器需要调用拷贝构造函数来创建对象,对应到我们程序的具体实现,就是容器需要我们提供一个这样的拷贝构造函数。

CMyAutoPtr(const _Myt& _Right)

显然我们提供是这样的拷贝构造函数。
智能指针仿真-002-值型智能指针_第1张图片
故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进行操作,此时其已不再持有原生指针。

你可能感兴趣的:(智能指针,unique-ptr,scoped-ptr,auto-ptr)