智能指针仿真-001-基础篇

1. 前言
本次技术研究关注C++中智能指针的原理,对各种类型指针进行仿真实现,将底层核心源码一段段曝光出来,从头到外理一遍。
对应文章有三篇,从简入深。
1) 《智能指针仿真-001-基础篇》,介绍所需基础知识。
2) 《智能指针仿真-002-值型智能指针》,仿真值型智能指针。
3) 《智能指针仿真-003-共享智能指针》,仿真共享智能指针。

平时看侯俊杰的书(翻译及原著)比较多,对于他严谨的研究风格深深折服,本次仿真之旅也算是学习他的研究之道,从最原始的代码入手抽取核心程序用显微镜来分析。
仿真源码所需要技术基础有。
1) 模板编程。
2) C++ 11 右值引用与移动语义。

2. 关于智能指针的思考
有些问题我们需要先思考一下。
1) 智能指针为什么称得上“智能”二字?
2) 智能指针的原理是什么?
3) 智能指针有哪些类型呢?
4) 各类型智能指针的应用场景?
5) 各智能指针有哪些坑以及如何解决?
6) 各智能指针到底是怎么实现的呢?

带着这些问题我们开启探寻之旅。
关于以上问题先做简单解答。
1) 智能指针可以自动回收所分配内存,所以称之为智能,用于防止内存泄漏。
2) 智能指针的基本原理:智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放。
3) 智能指针对应的类型主要有如下几种。
旧式stl智能指针:auto_ptr。
Boost智能指针:scoped_ptr、shared_ptr、weak_ptr。
C++ 11 tr1的智能指针:unique_ptr、shared_ptr、weak_ptr。
4) 根据所有权是否需要转移,指针是否需要存入容器,可采用不同指针。

3. 指针开胃菜
来探索一些指针的有趣操作。空指针与也指针操作类似,示例里面进行空指针源码实现。

3.1. 空指针与成员函数
程序源码如下。

#include <iostream>

class CTest
{
public:
    void print() {std::cout<<"CTest::print()"<<std::endl;}
};

int _tmain(int argc, _TCHAR* argv[])
{
    CTest* pTest = nullptr;
    pTest->print();
    return 0;
}

运行结果:CTest::print()。
思考一下:为什么对空指针操作,程序不爆机?

3.2. 空指针与成员变量
我们给测试类添加一个成员变量,再做下测试。

#include <iostream>

class CTest
{
public:
    CTest():m_nTest(0){}
    void print() {std::cout<<"CTest::print()"<<m_nTest<<std::endl;}

private:
    int m_nTest;
};

int _tmain(int argc, _TCHAR* argv[])
{
    CTest* pTest = nullptr;
    pTest->print();
    return 0;
}

运行结果:程序爆机。
思考一下:此时为何爆机?

3.3. 空指针与虚函数
对3.1程序调整,设print为虚函数。

#include <iostream>

class CTest
{
public:
    virtual void print() {std::cout<<"CTest::print()"<<std::endl;}
};

int _tmain(int argc, _TCHAR* argv[])
{
    CTest* pTest = nullptr;
    pTest->print();
    return 0;
}

运行结果:程序爆机。
思考一下:此时为何爆机?

3.4. 指针操作总结
指针操作行为要从C++对象模型来做下说明。
类对象的内存结构,有七个要素。
1) vptr:虚表指针。
2) vtbl:虚表。
3) virtual function:虚函数。
4) nonstatic data:非静态变量。
5) static data:静态变量。
6) nonstatic function:非静态函数。
7) static function:静态函数。

对应图如下。
智能指针仿真-001-基础篇_第1张图片
3.1中程序调用函数不涉及任何类对象内存,故运行正常。
3.2中程序调用函数需要操作成员变量,故会爆机。
3.3中程序调用函数需要操作虚表指针,故会爆机。

4. 右值引用与移动语义
C++ 11中引入了右值引用与移动语义。
详细描述参见:http://blog.csdn.net/zwvista/article/details/12306283。

4.1. 右值引用
引入右值引用目的:减少临时对象生成,减少了对象的拷贝成本,提高了程序运行效率。
右值引用语法:T&&。
左值转右值语法:static_cast

#include <iostream>

/// @brief 左值版本
void f(const int& lval){std::cout<<"f(const int& lval)"<<std::endl;}
/// @brief 右值版本 
void f(int&& rval){std::cout<<"f(int&& lval)"<<std::endl;}

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 10;
    f(a);///< 调用左值版本 
    f(int(0));///< 调用右值版本
    f(static_cast<int&&>(a));///< 调用右值版本
    f(std::move(a));///< 调用右值版本
    return 0;
}
    输出结果。
f(const int& lval)
f(int&& lval)
f(int&& lval)
f(int&& lval)

4.2. 移动语义
移动语义衍生出移动构造函数与移动赋值操作符。
对应源码如下。

struct X
{
    /// @brief 缺省构造器
    X() {std::cout<<"X()"<<std::endl;}
    /// @brief 缺省析构器
    ~X() {std::cout<<"~X()"<<std::endl;}
    /// @brief 拷贝构造器
    X(const X& that) {std::cout<<"X(const X& that)"<<std::endl;}
    /// @brief 移动构造器
    X(X&& that) {std::cout<<"X(X&& that)"<<std::endl;}
    /// @brief 拷贝赋值运算符
    X& operator=(const X& that)
    {
        std::cout<<"operator=(const X& that)"<<std::endl;
        return *this;
    }
    /// @brief 移动赋值运算符
    X& operator=(X&& that)
    {
        std::cout<<"operator=(X&& that)"<<std::endl;
        return *this;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    X a;                             //调用缺省构造器
    X b = a;                         //调用拷贝构造器
    X c = std::move(b);              //调用移动构造器
    b = a;                           //调用拷贝赋值运算符
    c = std::move(b);                //调用移动赋值运算符
    return 0;
}

运行结果。

X()
X(const X& that)
X(X&& that)
operator=(const X& that)
operator=(X&& that)
~X()
~X()
~X()

4.3. 程序示例
接下来看一下右值引用与移动操作符的完整示例,通过例子来领会一下移动构造函数比拷贝构造函数高效在什么地方。
源码如下。

/// @brief 完整示例
class MemoryBlock
{
public:
    /// @brief 构造器(初始化资源)
    explicit MemoryBlock(size_t length)
        : _length(length)
        , _data(new int[length])
    {
        std::cout<<"MemoryBlock(size_t length)"<<std::endl;
    }

    /// @brief 析构器(释放资源)
    ~MemoryBlock()
    {
        if (_data != nullptr)
        {
            delete[] _data;
        }
        std::cout<<"~MemoryBlock()"<<std::endl;
    }

    /// @brief 拷贝构造器(实现拷贝语义:拷贝that)
    MemoryBlock(const MemoryBlock& that)
        /// @brief 拷贝that对象所拥有的资源
        : _length(that._length)
        , _data(new int[that._length])
    {
        std::copy(that._data, that._data + _length, _data);
        std::cout<<"MemoryBlock(const MemoryBlock& that)"<<std::endl;
    }

    /// @brief 拷贝赋值运算符(实现拷贝语义:释放this + 拷贝that)
    MemoryBlock& operator=(const MemoryBlock& that)
    {
        if (this != &that)
        {
            // 释放自身的资源
            delete[] _data;

            // 拷贝that对象所拥有的资源
            _length = that._length;
            _data = new int[_length];
            std::copy(that._data, that._data + _length, _data);
        }
        std::cout<<"operator=(const MemoryBlock& that)"<<std::endl;
        return *this;
    }

    /// @brief 移动构造器(实现移动语义:移动that)
    MemoryBlock(MemoryBlock&& that)
        /// @brief 将自身的资源指针指向that对象所拥有的资源
        : _length(that._length)
        , _data(that._data)
    {
        // 将that对象原本指向该资源的指针设为空值
        that._data = nullptr;
        that._length = 0;
        std::cout<<"MemoryBlock(MemoryBlock&& that)"<<std::endl;
    }

    /// @brief 移动赋值运算符(实现移动语义:释放this + 移动that)
    MemoryBlock& operator=(MemoryBlock&& that)
    {
        if (this != &that)
        {
            /// @brief 释放自身的资源
            delete[] _data;

            /// @brief 将自身的资源指针指向that对象所拥有的资源
            _data = that._data;
            _length = that._length;

            /// @brief 将that对象原本指向该资源的指针设为空值
            that._data = nullptr;
            that._length = 0;
        }
        std::cout<<"operator=(MemoryBlock&& that)"<<std::endl;
        return *this;
    }
private:
    size_t _length; /// @brief 资源的长度
    int* _data; /// @brief 指向资源的指针,代表资源本身
};

MemoryBlock f()
{
    MemoryBlock mb(50);
    return mb;
}

int main()
{
    MemoryBlock a = f();///< 调用移动构造器,移动语义
    MemoryBlock b = a;///< 调用拷贝构造器,拷贝语义
    MemoryBlock c = std::move(a);///< 调用移动构造器,移动语义
    a = f();///< 调用移动赋值运算符,移动语义
    b = a;///< 调用拷贝赋值运算符,拷贝语义
    c = std::move(a);///< 调用移动赋值运算符,移动语义
    return 0;
}

分析源码可知:移动构造函数将右值引用that资源移到了this资源中,不会想拷贝构造一样再重新分配一份。

你可能感兴趣的:(智能指针,右值引用-移动语义)