智能指针仿真-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. 空指针与成员函数
程序源码如下。
运行结果:CTest::print()。
思考一下:为什么对空指针操作,程序不爆机?
3.2. 空指针与成员变量
我们给测试类添加一个成员变量,再做下测试。
运行结果:程序爆机。
思考一下:此时为何爆机?
3.3. 空指针与虚函数
对3.1程序调整,设print为虚函数。
运行结果:程序爆机。
思考一下:此时为何爆机?
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
4.2. 移动语义
移动语义衍生出移动构造函数与移动赋值操作符。
对应源码如下。
运行结果。
4.3. 程序示例
接下来看一下右值引用与移动操作符的完整示例,通过例子来领会一下移动构造函数比拷贝构造函数高效在什么地方。
源码如下。
分析源码可知:移动构造函数将右值引用that资源移到了this资源中,不会想拷贝构造一样再重新分配一份。
本次技术研究关注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资源中,不会想拷贝构造一样再重新分配一份。
相关文章推荐
- 黑马北京新闻项目连载(2)--->侧滑菜单栏、主页面Fragment搭建
- 求学生平均身高
- 检查mobileprovision 中的设备
- 如何把一个UITableView滚动到tableFooterView?
- 信息安全系统设计基础第一周学习总结
- ViewPager及其适配器FragmentPagerAdapter
- 批量下载图片
- 【Matlab学习笔记】【函数学习】size函数—图像的宽与高的获取
- js、jQuery中常用的一些方法
- 【经典算法】:狐狸抓兔子问题
- Android防止按钮连续点击
- 健身一定要注意的细节
- Fragment碎片
- Android中的Environment.getExternalStorageState使用
- 北京新网(xinnet.com)云服务器测验结果(严重不满)
- Java的一些基础知识
- 关于Adaboos选择最优弱分类器过程的理解
- javascript中的原型链,prototype与__proto__的关系
- C++字符串知识总结
- Linux各个版本资源下载