目录
移动语义
1、几个基本概念的理解
2、复制控制语义的函数
3、移动控制语义的函数
3.1、移动构造函数:
3.2、移动赋值函数
4.区别
5、std::move函数
6.代码演示:
资源管理与智能指针
一、C语言中的问题
二、C++的解决办法(RAII技术):
三、四种智能指针
1、auto_ptr.cc
2、unique_ptr
4、weak_ptr
四、为智能指针定制删除器
移动语义
1、几个基本概念的理解
左值、右值、 const 左值引用、右值引用?
可以取地址的是左值,不能取地址的就是右值。区分左值与右值的是能不能取地址。右值可能存在寄存器,也可能存在于栈上(短暂存在栈上)
右值包括:临时对象、匿名对象、字面值常量
const 左值引用可以绑定到左值与右值上面,称为万能引用。正因如此,也就无法区分传进来的参数是左值还是右值。左值引用:可以绑定到左值,但是不能绑定到右值。
const左值引用:既可以绑定到左值也可以绑定到右值。(正因如此,才将拷贝构造函数写成const左值引用)
右值引用:可以绑定到右值,但是不能绑定到左值。(正因如此,还能有移动语义的函数)
所以可以区分出传进来的参数到底是左值还是右值,进而可以区分.右值引用到底是左值还是右值?
这个与右值引用本身有没有名字有关,如果是 int &&rref = 10 ,右值引用本身就是左值,因为有名字。如果右值引用本身没有名字,那右值引用就是右值,如右值引用作为函数返回值。
什么是右值引用:
2、复制控制语义的函数
3、移动控制语义的函数
将内存的所有权从一个对象转移到另外一个对象,高效的移动用来替换效率低下的复制,对象的移动语义需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。
3.1、移动构造函数:
String(String &&rhs) : _pstr(rhs._pstr) {cout << "String(String &&)" << endl;rhs._pstr = nullptr; }
3.2、移动赋值函数
String &operator=(String &&rhs) {cout << "String &operator=(String &&)" << endl;if(this != &rhs)//1、自移动{delete [] _pstr;//2、释放左操作数_pstr = nullptr;_pstr = rhs._pstr;//3、浅拷贝rhs._pstr = nullptr;}return *this;//4、返回*this }
3.3举例
template <typename T> class DynamicArray { public:explicit DynamicArray(int size) :m_size{ size }, m_array{ new T[size] }{cout << "Constructor: dynamic array is created!\n";}virtual ~DynamicArray(){delete[] m_array;cout << "Destructor: dynamic array is destroyed!\n";}// 拷贝构造函数DynamicArray(const DynamicArray& rhs) :m_size{ rhs.m_size }{m_array = new T[m_size];for (int i = 0; i < m_size; ++i){m_array[i] = rhs.m_array[i];}cout << "Copy constructor: dynamic array is created!\n";}// 拷贝赋值运算符DynamicArray& operator=(const DynamicArray& rhs){cout << "Copy assignment operator is called\n";if (this == &rhs){return *this;}delete[] m_array;m_size = rhs.m_size;m_array = new T[m_size];for (int i = 0; i < m_size; ++i){m_array[i] = rhs.m_array[i];}return *this;}// 移动构造函数DynamicArray(DynamicArray&& rhs) :m_size{ rhs.m_size }, m_array{ rhs.m_array }{rhs.m_size = 0;rhs.m_array = nullptr;cout << "Move constructor: dynamic array is moved!\n";}// 移动赋值操作符DynamicArray& operator=(DynamicArray&& rhs){cout << "Move assignment operator is called\n";if (this == &rhs){return *this;}delete[] m_array;m_size = rhs.m_size;m_array = rhs.m_array;rhs.m_size = 0;rhs.m_array = nullptr;return *this;} private:T* m_array;int m_size; };int main() {DynamicArray<int> arr1(10);DynamicArray<int> arr2(move(arr1));system("pause");return 0; }
4.区别
5、std::move函数
将左值转换为右值,在内部其实上是做了一个强制转换, static_cast<T &&>(lvaule) 。将左值转换为右值后,左值就不能直接使用了,如果还想继续使用,必须重新赋值。
std::move()作用于内置类型没有任何作用,内置类型本身是左值还是右值,经过std::move()后不会改变。
6.代码演示:
#include <string.h> #include <iostream>using std::cout; using std::endl;class String { public:String(): _pstr(nullptr)/* : _pstr(new char[1]()) */{cout << "String()" << endl;}String(const char *pstr): _pstr(new char[strlen(pstr) + 1]()){cout << "String(const char *)" << endl;strcpy(_pstr, pstr);}//将拷贝构造函数和赋值运算符函数称为具有复制控制语义的函数String(const String &rhs): _pstr(new char[strlen(rhs._pstr) + 1]()){cout << "String(const String &)" << endl;strcpy(_pstr, rhs._pstr);}String &operator=(const String &rhs){cout << "String &operator=(const String &)" << endl;if (this != &rhs)//1、自复制{delete[] _pstr;//2、释放左操作数_pstr = nullptr;//3、深拷贝_pstr = new char[strlen(rhs._pstr) + 1]();strcpy(_pstr, rhs._pstr);}//4、返回*thisreturn *this;}//将移动构造函数和移动赋值运算符函数称为具有移动语义的函数////具有移动语义的函数优先于具有复制控制语义的函数执行////复制控制语义的函数编译器会自动生成,但是具有移动语义的//函数编译器是不会自动生成的,必须要手写////移动构造函数优先于拷贝构造函数执行的(优先级)//移动构造函数//String s3 = String("world");String(String &&rhs):_pstr(rhs._pstr){cout << "String(string &&)" << endl;rhs._pstr = nullptr;}//移动赋值运算符函数优先于赋值运算符函数执行的(优先级)//移动赋值运算符函数(移动赋值函数)//s4 = String("wuhan")//s4 = std::move(s4)//s4 = std::move(s5)String &operator=(String &&rhs){cout << "String &operator=(String &&)" << endl;if (this != &rhs)//1、自移动{delete[] _pstr;//2、释放左操作数_pstr = nullptr;_pstr = rhs._pstr;//3、浅拷贝rhs._pstr = nullptr;}return *this;//4、返回*this}~String(){cout << "~String()" << endl;if (_pstr){delete[] _pstr;_pstr = nullptr;}}friend std::ostream &operator<<(std::ostream &os, const String &rhs); private:char *_pstr; };std::ostream &operator<<(std::ostream &os, const String &rhs) {if (rhs._pstr){os << rhs._pstr;}return os; }void test() {String s1("hello");cout << "s1 = " << s1 << endl;cout << endl;String s2 = s1;cout << "s1 = " << s1 << endl;cout << "s2 = " << s2 << endl;cout << endl;// C++ C C风格转换为C++风格// 过渡String s3 = "world";//String("world"),临时对象/匿名对象,cout << "s3 = " << s3 << endl;/* &"world";//文字常量区,左值 *//* String("world");//右值 */cout << endl;String s4("wangdao");cout << "s4 = " << s4 << endl;cout << endl;s4 = String("wuhan");cout << "s4 = " << s4 << endl;//左右操作数是两个不一样对象/* String("wuhan") = String("wuhan"); */cout << endl;cout << "000000" << endl;//std::move可以将左值转换为右值,实质上没有做任何移动,只是//在底层做了强制转换static_cast<T &&>(lvalue)//如果以后不想使用某个左值,可以使用std::move将其转换为//右值,以后就不再使用了s4 = std::move(s4);cout << "s4 = " << s4 << endl;cout << "11111" << endl;s2 = std::move(s1);cout << "s1 = " << s1 << endl;cout << "2222" << endl;} int main() {test();return 0; }
运行结果:
#include <iostream> #include <string>using std::cout; using std::endl; using std::string;void test() {int a = 10;int b = 20;int *pflag = &a;string s1("hello");string s2("world");&a;//左值&b;//左值&pflag;//左值&*pflag;//左值&s1;//左值&s2;//左值(a + b);//右值,不能取地址(s1 + s2);//右值,不能取地址//const左值引用既可以绑定到左值,也可以绑定到右值const int &ref = a;const int &ref2 = 10;&ref;&ref2;//C++11之前是不能识别右值的,C++11之后新增语法可以识别右值//右值引用可以绑定到右值,能识别右值,但是右值引用不能//识别左值,绑定不了左值int &&rref = 10;//右值引用/* int &&rref2 = a;//error *///右值引用是左值还是右值?//所以,右值引用既可以是左值也可以是右值&rref;//右值引用在此处是左值//右值引用在作为函数参数的时候,体现出来的是左值的含义 }//右值引用可以是右值吗? //右值引用作为函数返回类型的时候,是右值 int &&func() {return 10; }int main(int argc, char **argv) {test();/* &func();//error, func是右值, */return 0; }
资源管理与智能指针
一、C语言中的问题
C语言在对资源管理的时候,比如文件指针,由于分支较多,或者由于写代码的人与维护的人不一致,导致分支没有写的那么完善,从而导致文件指针没有释放,所以可以使用C++的方式管理文件指针。
#include <iostream> #include <string>using std::cout; using std::endl; using std::string;class SafaFile { public://在构造时初始化资源,或者说托管资源SafaFile(FILE *fp): _fp(fp){cout << "SafaFile(FILE *)" << endl;}//提供若干访问资源的方法void write(const string &msg){fwrite(msg.c_str(), 1, msg.size(), _fp);}//void read();//在析构时候释放资源~SafaFile(){cout << "~SafaFile()" << endl;if (_fp){fclose(_fp);//如果不关掉,就表明文件没有关闭cout << "fclose(_fp)" << endl;}}private:FILE *_fp; };int main(int argc, char **argv) {string msg = "hello,world";SafaFile sf(fopen("test.txt", "a+"));//sf是栈对象,//其实就是利用栈对象sf的生命周期管理文件指针的资源sf.write(msg);/* SafaFile sf2 = sf;//error */return 0; }
二、C++的解决办法(RAII技术):
资源管理 RAII 技术,利用对象的生命周期管理程序资源(包括内存、文件句柄、锁等)的技术,因为对象在离开作用域的时候,会自动调用析构函数。
关键:要保证资源的释放顺序与获取顺序严格相反。正好是析构函数与构造函数的作用。
RAII常见特征
1、在构造时初始化资源,或者托管资源。
2、析构时释放资源。
3、一般不允许复制或者赋值(值语义-对象语义)
4、提供若干访问资源的方法。区分:
值语义:可以进行复制与赋值。
对象语义:不能进行复制与赋值(世界上一般没有两个重复的人)一般使用两种方法达到要求:
(1)、将拷贝构造函数和赋值运算符函数设置为私有的就 ok 。
(2)、将拷贝构造函数和赋值运算符函数使用=delete.#include <iostream>using std::cout; using std::endl;class Point { public:Point(int ix = 0, int iy = 0): _ix(ix), _iy(iy){cout << "Point(int = 0, int = 0)" << endl;}void print() const{cout << "(" << _ix<< ", " << _iy<< ")" << endl;}~Point(){cout << "~Point()" << endl;} private:int _ix;int _iy; };template<typename T> class RAII { public://在构造函数中初始化资源RAII(T *data): _data(data){cout << "RAII(T *)" << endl;}//在析构函数中释放资源~RAII(){cout << "~RAII()" << endl;if (_data){delete _data;//假如指针是new出来_data = nullptr;}}//提供若干访问资源的方法T *operator->(){return _data;}T &operator*(){return *_data;}T *get() const{return _data;}//重置数据成员_datavoid reset(T *data){if (_data){delete _data;_data = nullptr;}_data = data;}//不允许复制或者赋值//C++11的写法=deleteRAII(const RAII &rhs) = delete;RAII &operator=(const RAII &rhs) = delete;//C++98(传统C++方法)设置为私有的 private:/* RAII(const RAII &rhs); *//* RAII &operator=(const RAII &rhs); */ private:T *_data; };int main(int argc, char **argv) {/* RAII<int> pInt(new int(10)); *///pt本身不是指针,但是具备指针的功能(智能指针)RAII<Point> pt(new Point(1, 2));//pt栈对象pt->print();(*pt).print();/* pt.operator->()->print();//ok *//* RAII<Point> pt2 = pt;//拷贝构造函数,error */RAII<Point> pt3(new Point(3, 4));/* pt3 = pt;//赋值运算符函数,error */return 0; }
运行结果:
三、四种智能指针
智能指针的两篇优秀文章:
【C++】智能指针详解_c++智能指针-CSDN博客https://blog.csdn.net/qq_53268869/article/details/124551345?spm=1001.2014.3001.5506c++11之智能指针_智能指针c++11-CSDN博客https://blog.csdn.net/qq_56673429/article/details/124837626?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171124459316800186573033%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171124459316800186573033&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-8-124837626-null-null.nonecase&utm_term=C%2B%2B%E4%B9%8B%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88&spm=1018.2226.3001.4450
RAII的对象就有智能指针的雏形。其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源.
智能指针不是指针,是一个管理指针的类,用来存储指向动态分配(堆空间)对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源.
1、auto_ptr.cc
void test() {int *pt = new int(10);auto_ptr<int> ap(pt);cout << "*ap = " << *ap << endl;cout << "ap.get() = " << ap.get() << endl;cout << "pt = " << pt << endl;cout << endl << endl;auto_ptr<int> ap2(ap);//表面上执行拷贝构造函数,但是在底层已经发生了所有权(资源的) 的转移//该智能指针存在缺陷cout << "*ap2 = " << *ap2 << endl;cout << "*ap = " << *ap << endl;//core dump }
具体的内部实现:
template<class T>class auto_ptr{public:auto_ptr(T* ptr=nullptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr; //管理权转移}auto_ptr<T>& operator = (auto_ptr<T>& ap){if (this != *ap) {delete _ptr;_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}~SmartPtr(){if (_ptr)delete _ptr;}T& operator *(){return *_ptr;}T* operator ->(){return _ptr;}private:T* _ptr;};
2、unique_ptr
void test() {unique_ptr<int> up(new int(10));cout << "*up = " << *up << endl;cout << "up.get() = " << up.get() << endl;//获取托管的指针的值cout << endl << endl;/* unique_ptr<int> up2(up);//error,独享资源的所有权,不能进行复制 */cout << endl << endl;unique_ptr<int> up3(new int(10));/* up3 = up;//error,不能进行赋值操作 */ cout << endl << endl;unique_ptr<int> up4(std::move(up));//通过移动语义转移up的所有权cout << "*up4 = " << *up4 << endl;cout << "up4.get() = " << up4.get() << endl;cout << endl << endl;unique_ptr<Point> up5(new Point(3, 4));//通过移动语义转移up的所有权vector<unique_ptr<Point>> numbers;numbers.push_back(unique_ptr<Point>(new Point(1, 2)));numbers.push_back(std::move(up5)); }
具体的内部实现:
template<class T>class unique_ptr{public:unique_ptr(T* ptr = nullptr):_ptr(ptr){}//防拷贝unique_ptr(unique_ptr<T>& ap) = delete;unique_ptr<T>& operator = (unique_ptr<T>& ap) = delete;~SmartPtr(){if (_ptr)delete _ptr;}T& operator *(){return *_ptr;}T* operator ->(){return _ptr;}private:T* _ptr;};
几个注意点:
1.无法进行复制、赋值操作std::unique_ptr<int> ap(new int(88 );std::unique_ptr<int> one (ap) ; // 会出错std::unique_ptr<int> two = one; //会出错2.可以进行移动构造和移动赋值操作unique_ptr<int> GetVal( ){unique_ptr<int> up(new int(88 );return up;}unique_ptr<int> uPtr = GetVal(); //ok实际上上面的的操作有点类似于如下操作unique_ptr<int> up(new int(88 );unique_ptr<int> uPtr2 = std::move(up) ; //这里是显式的所有权转移. 把up所指的内存转给uPtr2了,而up不再拥有该内存.3.可做为容器元素unique_ptr<int> sp(new int(88));vector<unique_ptr<int> > vec;vec.push_back(std::move(sp));//vec.push_back( sp ); 这样不行,会报错的.//cout<<*sp<<endl;但这个也同样出错,说明sp添加到容器中之后,它自身报废了.
3、shared_ptr
#include <iostream> using namespace std; #include <string> #include <memory>class Test { public:Test() : m_num(0){cout << "construct Test..." << endl;}Test(int x) : m_num(0){cout << "construct Test, x = " << x << endl;}Test(string str) : m_num(0){cout << "construct Test, str = " << str << endl;}~Test(){cout << "destruct Test..." << endl;}void setValue(int v){this->m_num = v;}void print(){cout << "m_num: " << this->m_num << endl;}private:int m_num; };int main() {/*-------------------------- 一,初始化智能指针shared_ptr ------------------------------*///1.通过构造函数初始化shared_ptr<int> ptr1(new int(3));cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;//2.通过移动和拷贝构造函数初始化shared_ptr<int> ptr2 = move(ptr1);//此时ptr1的空间不再使用cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;shared_ptr<int> ptr3 = ptr2;cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;//3.通过 std::make_shared初始化shared_ptr<int> ptr4 = make_shared<int>(8);shared_ptr<Test> ptr5 = make_shared<Test>(7);shared_ptr<Test> ptr6 = make_shared<Test>("GOOD LUCKLY!");//4.通过reset初始化ptr6.reset(); //重置ptr6, ptr6的引用基数为0cout << "ptr6管理的内存引用计数: " << ptr6.use_count() << endl;ptr5.reset(new Test("hello"));cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;cout << endl;cout << endl;/*-------------------------- 二,共享智能指针shared_ptr的使用 ------------------------------*///1.方法一Test* t = ptr5.get();cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;t->setValue(1000);t->print();//2.方法二ptr5->setValue(7777);ptr5->print();printf("\n\n");/*------------------------------------ 三,指定删除器 -----------------------------------*///1.简单举例shared_ptr<Test> ppp(new Test(100), [](Test* t) {//释放内存cout << "Test对象的内存被释放了......." << endl;delete t;});printf("----------------------------------------------------------------------\n");//2.如果是数组类型的地址,就需要自己写指定删除器,否则内存无法全部释放//shared_ptr<Test> p1(new Test[5], [](Test* t) {// delete[]t;// });//3.也可以使用c++给我们提供的 默认删除器函数(函数模板)shared_ptr<Test> p2(new Test[3], default_delete<Test[]>());//4.c++11以后可以这样写 也可以自动释放内存shared_ptr<Test[]> p3(new Test[3]);return 0; }
具体的内部实现:
template<class T>class shared_ptr{public:shared_ptr(T*ptr =nullptr):_ptr(ptr),_pcount(new int(1)){}//拷贝构造shared_ptr(const T& sp)_ptr(sp._ptr),_pcount(sp._pcount){++(*_pcount);}//赋值拷贝shared_ptr<T>& operator = (shared_ptr<T>& sp){if (_ptr != sp._ptr) {if (--(*_pcount) == 0){delete _pcount;delete _ptr;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}T& operator *(){return *_ptr;}T* operator ->(){return _ptr;}~shared_ptr(){if (--(*_pcount) == 0 && _ptr) {delete _pcount;delete _ptr;}}private:T* _ptr;int* _pcount;};
指针存在的问题:
举例:
问题:循环引用--无法释放对象,造成内存泄露 #include <iostream> #include <memory>class Parent; class Child;typedef std::shared_ptr<Parent> parent_ptr; typedef std::shared_ptr<Child> child_ptr;class Child { public:Child() { std::cout << "Child..." << std::endl; }~Child() { std::cout << "~Child..." << std::endl; }parent_ptr parent_; };class Parent { public:Parent() { std::cout << "Parent..." << std::endl; }~Parent() { std::cout << "~Parent..." << std::endl; }child_ptr child_; };int main(void) {parent_ptr parent(new Parent);child_ptr child(new Child);parent->child_ = child;//parent.operator->()->child_ = child;child->parent_ = parent;return 0; }
4、weak_ptr
使用:
#include <iostream> #include <memory> using namespace std;int main() {shared_ptr<int> sp(new int);weak_ptr<int> wp1;weak_ptr<int> wp2(wp1);weak_ptr<int> wp3(sp);weak_ptr<int> wp4;wp4 = sp;weak_ptr<int> wp5;wp5 = wp3;return 0; }
内部实现:
template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(shared_ptr<T>& sp):_ptr(sp.get()),_pcount(sp.use_count()){}weak_ptr(weak_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){}weak_ptr& operator = (shared_ptr<T>& sp){_ptr = sp.get();_pcount = sp.use_count();return *this;}weak_ptr& operator = (weak_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count(){return *_pcount;}private:T* _ptr;int* _pcount;};
解决循环引用问题:
#include <iostream> #include <memory>class X { public:X() { std::cout << "construct X" << std::endl; }~X() { std::cout << "~destruct X" << std::endl; }void Fun(){std::cout << "Fun() " << std::endl;} }; int main() {std::weak_ptr<X> p;{std::shared_ptr<X> p2(new X);std::cout << p2.use_count() << std::endl;p = p2;std::cout << "after p = p2" << std::endl;std::cout << p2.use_count() << std::endl;std::shared_ptr<X> p3 = p.lock();//提升成功if (!p3){std::cout << "object is destroyed" << std::endl;}else{p3->Fun();std::cout << p3.use_count() << std::endl;}}//new X 已经被释放了std::shared_ptr<X> p4 = p.lock();//提升失败if (!p4)std::cout << "object is destroyed 2" << std::endl;elsep4->Fun();return 0; }
四、为智能指针定制删除器
很多时候我们都用new来申请空间,用delete来释放。库中实现的各种智能指针,默认也都是用delete来释放空间,但是若我们采用malloc申请的空间或是用fopen打开的文件,这时我们的智能指针就无法来处理,因此我们需要为智能指针定制删除器,提供一个可以自由选择析构的接口,这样,我们的智能指针就可以处理不同形式开辟的空间以及可以管理文件指针。
自定义智能指针的方式有两种,函数指针与仿函数(函数对象)函数指针的形式:
template<class T> void Free(T* p) {if (p)free(p); } template<class T> void Del(T* p) {if (p)delete p; } void FClose(FILE* pf) {if (pf)fclose(pf); } //定义函数指针的类型 typedef void(*DP)(void*); template<class T> class SharedPtr { public:SharedPtr(T* ptr = NULL ,DP dp=Del):_ptr(ptr), _pCount(NULL), _dp(dp){if (_ptr != NULL){_pCount = new int(1);}} private:void Release(){if (_ptr&&0==--GetRef()){//delete _ptr;_dp(_ptr); delete _pCount;}}int& GetRef() {return *_pCount; } private:T* _ptr;int* _pCount;DP _dp; };
删除器的使用: