C++之移动语义与智能指针

目录

移动语义

1、几个基本概念的理解

2、复制控制语义的函数

3、移动控制语义的函数

3.1、移动构造函数:

3.2、移动赋值函数

4.区别

5、std::move函数

6.代码演示:

资源管理与智能指针

一、C语言中的问题

二、C++的解决办法(RAII技术):

三、四种智能指针

1、auto_ptr.cc

2、unique_ptr

3、shared_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、返回*this
		return *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;
	}

	//重置数据成员_data
	void reset(T *data)
	{
		if (_data)
		{
			delete _data;
			_data = nullptr;
		}
		_data = data;
	}

	//不允许复制或者赋值
	//C++11的写法=delete
	RAII(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博客icon-default.png?t=N7T8https://blog.csdn.net/qq_53268869/article/details/124551345?spm=1001.2014.3001.5506c++11之智能指针_智能指针c++11-CSDN博客icon-default.png?t=N7T8https://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的引用基数为0
	cout << "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;
	else
		p4->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;
};

删除器的使用:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/484153.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

水果检测15种YOLOV8

水果检测15种YOLOV8&#xff0c;只需要OPENCV&#xff0c;采用YOLOV8训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV调用&#xff0c;支持C/PYTHON/ANDROID开发

宝塔面板优惠券在哪里领取?

随着科技的飞速发展&#xff0c;网站搭建和管理变得越来越简单。宝塔面板作为一款优秀的服务器管理面板&#xff0c;深受广大站长用户喜爱。为了帮助站长们更好地使用宝塔面板&#xff0c;本文将为大家详细介绍如何领取宝塔面板优惠券。 一、宝塔面板优惠券领取入口 领取入口&a…

Ubuntu Desktop 安装谷歌拼音输入法

Ubuntu Desktop 安装谷歌拼音输入法 1. Installation1.1. 汉语语言包​1.2. 谷歌拼音输入法1.3. 安装语言包1.4. 键盘输入方式系统1.5. 重启电脑1.6. 输入法配置 2. configuration2.1. Text Entry Settings… 3. ExecutionReferences 1. Installation 1.1. 汉语语言包 strong…

LeetCode刷题日志-34在排序数组中查找元素的第一个和最后一个位置

思路&#xff1a;用两次二分查找&#xff0c;分别查找指定元素的第一个和最后一个位置。 直接看代码 // 两次二分查找&#xff0c;分开查找第一个和最后一个 class Solution {public int[] searchRange(int[] nums, int target) {int[] reslut new int[]{-1, -1};int left 0…

virtualbox导入vdi

新建虚拟机 点击新建 输入新建属性 配置cpu和内存 虚拟硬盘 这里选择已有的vdi文件 摘要 这里点击完成 虚拟机添加成功 点击启动,启动虚拟机 注意 这个时候的ip,还是以前镜像的ip,如果两个镜像一起启动

双系统安装03--在已有麒麟KOS基础上安装Windows10

原文链接&#xff1a;双系统安装03–在已有麒麟KOS基础上安装Windows10 Hello&#xff0c;大家好啊&#xff01;继我们之前讨论的关于双系统安装的系列文章之后&#xff0c;今天我将带给大家这个系列的第三篇——在已有的麒麟桌面操作系统上安装Windows 10。对于想要在使用麒麟…

提升水库大坝安全与效率:现代技术云平台的应用

在我国&#xff0c;水库大坝的数量居世界之首&#xff0c;它们在推动国民经济发展中扮演着不可或缺的角色。然而&#xff0c;要想让这些水利工程充分发挥其价值&#xff0c;不仅需要精准的调度与高效的管理&#xff0c;更重要的是要确保其安全无虞。一旦发生事故&#xff0c;后…

阿里云倚天服务器是什么?倚天服务器c8y、g8y和r8y详细介绍

阿里云倚天云服务器CPU采用倚天710处理器&#xff0c;租用倚天服务器c8y、g8y和r8y可以享受优惠价格&#xff0c;阿里云服务器网aliyunfuwuqi.com整理倚天云服务器详细介绍、倚天710处理器性能测评、CIPU架构优势、倚天服务器使用场景及生态支持&#xff1a; 阿里云倚天云服务…

海外媒体发稿:7款爆款标题生成器解析-华媒舍

科普文章是将科学知识普及给大众的一种方式&#xff0c;而一个引人入胜的标题往往是吸引读者的第一步。在科普领域中&#xff0c;标题的吸引力至关重要。以下将介绍7款风靡科普界的爆款标题生成器&#xff0c;帮助你创作出引人入胜的科普文章。 1. 情感引爆 情感是人类行为中的…

CSS时钟案例

文章目录 1. 演示效果2. 分析思路3. 代码实现 1. 演示效果 2. 分析思路 背景是表盘&#xff0c;不用自己制作然后用CSS的定位做时针&#xff0c;分针和秒针黑点用伪元素::after生成转动用animation实现 3. 代码实现 <!DOCTYPE html> <html lang"en">&…

【数据结构】快速排序(用递归)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解快速排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 快速排序2.1 hoare版本2.2 挖坑法2.3 前后指针法2.4 快速排序优化三数取中法…

弥合认知差距,扫除IT服务交付中的“拦路虎”

文章目录 编辑推荐内容简介作者简介作者简介&#xff1a;译者简介&#xff1a; 精彩书评目录 按需交付服务从来都不容易。成功的交付是以一种符合客户预期的一致性、可靠性、安全性、隐私性和成本效益的方式交付客户所需的服务。无论服务提供商提供的是 IT 服务&#xff0c;还是…

【C语言】——指针四:字符指针与函数指针变量

【C语言】——指针四&#xff1a;字符指针与函数指针变量 一、字符指针二、函数指针变量2.1、 函数指针变量的创建2.2、两段有趣的代码 三、typedef关键字3.1、typedef的使用3.2、typedef与define比较 四、函数指针数组 一、字符指针 在前面的学习中&#xff0c;我们知道有一种…

力扣|两数相加|链表

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

C语言操作符和数据类型的存储详解

CSDN成就一亿技术人 目录​​​​​​​ 一.操作符 一.算数操作符&#xff1a; 二.位移操作符&#xff1a; 三.位操作符&#xff1a; 四.赋值操作符&#xff1a; 五.单目操作符&#xff1a; 六.关系操作符&#xff1a; 七.逻辑操作符&#xff1a; 八.条件操作符&…

【数据结构】归并排序(用递归)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解归并排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 归并排序代码 一. 基本思想 在讲归并排序之前&#xff0c;问问自己&#x…

Nexus3 Docker 私有仓库

Nexus3 Docker 私有仓库 安装并部署 Nexus3 $ docker search nexus3$ docker pull sonatype/nexus3$ mkdir /home/tester/data/docker/nexus3/sonatype-work $ sudo chown -R 200 /home/tester/data/docker/nexus3/sonatype-work$ docker run -d --namenexus3 \ --restartalw…

【prometheus-operator】k8s监控redis

1、准备exporter https://github.com/oliver006/redis_exporter oliver006-redis_exporter-amd64.tar # 安装镜像 docker load -i oliver006-redis_exporter-amd64.tar # 上传镜像 docker tag oliver006/redis_exporter ip/monitor/redis_exporter:latest docker push ip/mo…

算法---前缀和练习-2(和为k的子数组)

和为k的子数组 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 创建一个无序映射&#xff08;哈希表&#xff09; hash&#xff0c;用于统计前缀和的出现次数。初始时&#xff0c;将前缀和为 0 的次数设为 1&#xff0c;表示…

抖音IP属地怎么更改

抖音是一个非常受欢迎的短视频平台&#xff0c;吸引了无数用户在上面分享自己的生活和才艺。然而&#xff0c;随着快手的火爆&#xff0c;一些用户开始担心自己的IP地址会被他人获取&#xff0c;引起个人隐私风险。那么&#xff0c;抖音用户又该如何更改到别的地方呢&#xff1…