您的位置:首页 > 其它

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

2015-09-20 14:52 337 查看
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:静态函数。

对应图如下。



3.1中程序调用函数不涉及任何类对象内存,故运行正常。

3.2中程序调用函数需要操作成员变量,故会爆机。

3.3中程序调用函数需要操作虚表指针,故会爆机。

4. 右值引用与移动语义

C++ 11中引入了右值引用与移动语义。

详细描述参见:/article/2399321.html

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资源中,不会想拷贝构造一样再重新分配一份。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: