C++11知识点总结(下)智能指针等

本文详细介绍了C++11中的右值引用概念,包括右值与左值的区别,右值引用的用途,如优化拷贝构造函数,以及std::move和std::forward的作用。接着,文章讨论了引用折叠现象,并展示了如何使用std::forward实现完美转发。此外,文章还深入讲解了智能指针(shared_ptr、weak_ptr和unique_ptr)的使用,包括它们的初始化、功能以及在内存管理中的重要性,特别是如何解决循环引用问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

右值引用

右值 与 左值

c++11增加了一个新的类型,右值引用,记作:&& 

● 左值是指在内存中有明确的地址,我们可以找到这块地址的数据(可取地址)。

● 右值是只提供数据,无法找到地址(不可取地址)。

○ 所有有名字的变量都是左值,而右值是匿名的。

一般情况下位于等号左边的是左值,位于等号右边的是右值,但是也可以出现左值给左值赋值的情况。

c++11中右值分为两种情况:一个是将亡值,另一个是纯右值。

● 纯右值:非引用返回的临时变量,运算表达式产生的临时变量,原始字面量,lambda表达式等。

● 将亡值:与右值引用相关的表达式,比如:T&& 类型函数的返回值,std::move()的返回值等。

右值引用

右值引用就是对右值引用的类型。因为右值是匿名的,所以我们只能通过引用的方式找到它。无论是左值引用还是右值引用都必须被初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用,该右值所占的内存又可以被使用。

int&& value = 520; // 右值引用,520是字面量,是右值

class Test
{
public:
	Test()
	{
		cout << "构造函数" << endl;
	}
	Test(const Test& a)
	{
		cout << "拷贝构造函数" << endl;
	}
};

Test getObj()
{
	return Test();
}

int main()
{
	int a1;
	//int &&a2 = a1;        // 报错 右值引用不能被左值初始化
	//Test& t1 = getObj();   // 右值不能初始化左值引用
	Test && t2 = getObj(); //函数返回的临时对象是右值,可以被引用
	const Test& t3 = getObj();// 常量左值引用是万能可以接收左值,右值,常量左值,常量右值
	const int& t3 = a1; //被左值初始化
	return 0;
}

右值引用的用处

在c++用对象初始化对象时会调用拷贝构造,如果这个对象占用堆内存很大,那么这个拷贝的代价就是非常大的,在某些情况,如果想要避免对象的深拷贝,就可以使用右值引用进行性能的优化。

class Test
{
public:
	Test() : m_num(new int(100))
	{
		cout << "构造函数" << endl;
	}

	Test(const Test& a) : m_num(new int(*a.m_num))
	{
		cout << "拷贝构造函数" << endl;
	}

	~Test()
	{
		delete m_num;
	}

	int* m_num;
};

Test getObj()
{
	Test t;
	return t;
}

int main()
{
	Test t = getObj();
	cout << "t.m_num: " << *t.m_num << endl;
	return 0;
};

这段代码在调用Test t = getObj();  的时候调用了拷贝构造函数,对返回的临时对象进行了深拷贝得到了对象t,在getObj函数中创建的对象虽然进行了内存申请操作,但是没有使用就被释放掉了。如果我们在函数结束后仍然可以利用在函数里面申请的空间就极大的节省了创建对象和释放对象的时间。这个操作就需要我们的右值引用来完成。

右值引用具有移动语义,移动语义可以将堆区资源,通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象的创建,拷贝以及销毁,大幅度提高性能。

class Test
{
public:
	Test() : m_num(new int(100))
	{
		cout << "构造函数" << endl;
	}

	Test(const Test& a) : m_num(new int(*a.m_num))
	{
		cout << "拷贝构造函数" << endl;
	}

	// 添加移动构造函数,参数是右值引用
	Test(Test&& a) : m_num(a.m_num) 
	{
		a.m_num = nullptr;
		cout << "移动构造函数" << endl;
	}

	~Test()
	{
		delete m_num;
		cout << "析构函数" << endl;
	}

	int* m_num;
};

Test getObj()
{
	Test t;
	return t;
}

int main()
{
	Test t = getObj(); // 因为getObj 返回的是右值,所以调用移动构造函数
	cout << "t.m_num: " << *t.m_num << endl;
	return 0;
};

● 在上面的代码中添加了 移动构造函数(参数为右值引用类型),这样在进行 Test t = getObj();并没有调用构造函数进行深拷贝,而是调用的(浅拷贝)移动构造,提高了性能。

● 本例子中,getObj()返回值是一个右值,在进行赋值操作的时候如果  等号 右边是一个右值,那么移动构造函数就会被调用。

结论:需要动态申请大量的资源的类,应该设计移动构造,提高程序的效率。需要注意的是在提供移动构造的同时,一般也会提供左值引用拷贝构造函数,左值初始化新对象时会走拷贝构造。

move

c++11添加了右值引用,却不能左值初始化右值引用,在一些特定的情况下免不了需要左值初始化右值引用(用左值调用移动构造),如果想要用左值初始化一个右值引用需要借助std::move()  函数。move() 函数可以将左值转换为右值。

#include<iostream>
using namespace std;
class Test
{
public:
	int a = 3;
	int* m_num = &a;
	Test() : m_num(new int(100))
	{
		cout << "构造函数" << endl;
	}

	Test(const Test& other) : m_num(new int(*other.m_num))
	{
		cout << "拷贝构造函数" << endl;
	}

	// 添加移动构造函数
	Test(Test&& a) : m_num(a.m_num)
	{
		a.m_num = nullptr;
		cout << "移动构造函数" << endl;
	}

	~Test()
	{
		delete m_num;
		cout << "析构函数" << endl;
	}
	
};
Test getObj()
{
	Test t;
	return t;
}
int main()
{
	Test t = getObj();
	Test t2 = move(t); //此处调用移动构造,因为move返回一个右值
	return 0;
};

引用折叠

c++中,并不是所有情况下&&都代表右值引用,在模板和自动类型推导(auto)中,如果是模板参数需要指定为T&&,如果是自动类型推导需要指定为auto &&,这两种情况下&&被称作 未定的引用类型。另外 const T &&表示 一个右值引用,不是未定引用类型。

template<typename T>
void fun(T&& param)
{
    work(forward<T>(param))
}
int main()
{
	fun(10); //对于 f(10) 来说传入的实参 10 是右值,因此 T&& 表示右值引用
	int x = 1;
	fun(x);//对于 f(x) 来说传入的实参是 x 是左值,因此 T&& 表示左值引用
	return 0;
}

因为T&& 或者auto&&这种未定引用类型作为参数时,有可能被推导成右值引用,也有可能被推导为左值引用,在进行类型推导时右值引用会发生变化,这种变化被称作引用折叠。折叠规则如下:

● 通过右值 推导T&&或者auto&& 得到的是一个右值引用类型,const T &&表示 一个右值引用

● 通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导T&& 或者auto&&得到的是一个左值引用类型

int main()
{
	int&& a1 = 1;       //右值推导为               右值引用
	auto&& bb = a1; //右值引用推导为       左值引用
	auto&& bb1 = 2; //右值推导为              右值引用

	int a2 = 1;		
	int &a3 = a2;	      //左值推导为                      左值引用
	auto&& cc = a3;    //左值引用推导为               左值引用
	auto&& cc1 = a2; //左值推导为                       左值引用

	const int& s1 = 1; // 常量左值引用
	const int&& s2 = 1;// 常量右值引用
	auto&& dd = s1;    // 常量左值引用推导为              左值引用
	auto&& ee = s2;   // 常量右值引用推导为               左值引用
	return 0;
}

forward

右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,他就变成了一个左值(当右值被命名是编译器会认为他是个左值),并不是原来的类型了。如果按照参数原来的类型转发到另一个函数,可以使用c++11的  std::forward()函数,该函数实现的功能称之为完美转发。

// 函数原型
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;

template <class T> T&& forward(typenameremove_reference<T>::type&& t) noexcept;

// 精简之后的样子
std::forward<T>(t);

● 当T为左值引用类型时,t将会被转换为左值

● 当T不是左值引用类型时,t将被转换为T类型的右值

#include <iostream>
using namespace std;

template<typename T>
void printValue(T& t)
{
	cout << "左值引用: " << t << endl;
}

template<typename T>
void printValue(T&& t)
{
	cout << "右值引用:" << t << endl;
}

template<typename T>
void testForward(&& v)
{
	printValue(v);
	printValue(move(v));
	printValue(forward<T>(v));
	cout << endl;
}

int main()
{
	testForward(520);
	int num = 1314;
	testForward(num);
	testForward(forward<int>(num));
	testForward(forward<int&>(num));
	testForward(forward<int&&>(num));
	return 0;
}
#include<iostream>
using namespace std;

template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};
template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	void PushBack(T&& x)
	{
		//Insert(_head, x);
		Insert(_head, std::forward<T>(x));
	}
	void PushFront(T&& x)
	{
		//Insert(_head->_next, x);
		Insert(_head->_next, std::forward<T>(x));
	}
	void Insert(Node* pos, T&& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = x; 		
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};

智能指针

c++中不像java自带垃圾回收机制,必须释放掉分配的内存,否则就会造成内存泄漏。因此c++11引入了智能指针。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动的销毁分配的对象,防止内存泄漏。智能指针的核心实现技术是引用技术,每使用它一次内部引用计数加1,每析构一次内部引用计数减1,减为0时,删除原始指针指向的堆区内存,使用智能指针需要引用头文件<memory>

● std::shared_ptr:共享的智能指针

● std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视 shared_ptr 的,解决循环引用问题。

● std::unique_ptr:独占的智能指针

share_ptr(共享智能指针)

初始化

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针share_ptr是一个模板类,如果进行初始化有三种方式如下:

● 通过构造函数初始化

● std::make_shared辅助函数

● reset方法

共享智能指针对象初始化完毕之后就指向了要管理的那块堆区内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count。

构造函数初始化
	// 使用智能指针管理一块 int 型的堆内存
	shared_ptr<int> ptr1(new int(520));	
	cout << "ptr1管理的内存引用计数:" << ptr1.use_count() << endl;
	//使用智能指针管理一块字符数组对应的堆内存
	shared_ptr<char> ptr2(new char[520]); 
	cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl;
	shared_ptr<int> ptr3;
	cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
	// 创建智能指针对象, 初始化为空
	shared_ptr<int> ptr4(nullptr);
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

打印结果如下:
        ptr1管理的内存引用计数: 1
        ptr2管理的内存引用计数: 1
        ptr3管理的内存引用计数: 0
        ptr4管理的内存引用计数: 0

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数+1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数为0。另外,不要使用一个原始指针初始化多个shared_ptr。

拷贝和移动构造函数初始化

当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被调用了。

        //构造函数
	shared_ptr<int> ptr1(new int(520));
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;

	//拷贝构造函数
	shared_ptr<int> ptr2(ptr1);
	cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
	shared_ptr<int> ptr3 = ptr1;
	cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;

	//移动构造函数
	shared_ptr<int> ptr4(std::move(ptr1));
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
	std::shared_ptr<int> ptr5 = std::move(ptr2);
	cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
打印结果如下:
        ptr1管理的内存引用计数: 1
        ptr2管理的内存引用计数: 2
        ptr3管理的内存引用计数: 3
        ptr4管理的内存引用计数: 3
        ptr5管理的内存引用计数: 3
std::make_shared初始化

通过c++11提供的std::make_shared()就可以完成内存对象的创建并将其初始化给智能指针

#include <iostream>
#include <string>
#include <memory>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "无参构造函数" << endl;
	}
	Test(int x)
	{
		cout << "int类型构造函数 " << x << endl;
	}
	Test(string str)
	{
		cout << "string类型的构造函数" << str << endl;
	}
	~Test()
	{
		cout << "析构函数" << endl;
	}
};

int main()
{
	// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
	shared_ptr<int> ptr1 = make_shared<int>(520);
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;

	shared_ptr<Test> ptr2 = make_shared<Test>();
	cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;

	shared_ptr<Test> ptr3 = make_shared<Test>(520);
	cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;

	shared_ptr<Test> ptr4 = make_shared<Test>("QQQQ");
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
	return 0;
}
打印结果如下:
        ptr1管理的内存引用计数: 1
        无参构造函数
        ptr2管理的内存引用计数: 1
        int类型构造函数 520
        ptr3管理的内存引用计数: 1
        string类型的构造函数QQQQ
        ptr4管理的内存引用计数: 1
        析构函数
        析构函数
        析构函数

如果使用拷贝的方式初始化共享智能指针,这两个对象会同时管理同一块内存,堆内存对应的引用技术也会增加。如果使用移动构造的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用技术不会增加。

reset方法初始化
#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
	// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
	shared_ptr<int> ptr1 = make_shared<int>(520);
	shared_ptr<int> ptr2 = ptr1;
	shared_ptr<int> ptr3 = ptr1;
	shared_ptr<int> ptr4 = ptr1;
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
	cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
	cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

	ptr4.reset();
	cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
	cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
	cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
	cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

	shared_ptr<int> ptr5;
	ptr5.reset(new int(250));
	cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
	return 0;
}
打印结果如下:
    ptr1管理的内存引用计数: 4
    ptr2管理的内存引用计数: 4
    ptr3管理的内存引用计数: 4
    ptr4管理的内存引用计数: 4
    
    ptr1管理的内存引用计数: 3
    ptr2管理的内存引用计数: 3
    ptr3管理的内存引用计数: 3
    ptr4管理的内存引用计数: 0
    
    ptr5管理的内存引用计数: 1

对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值得时候,调用reset会使引用计数减1.

获取原始指针

get()函数

int main()
{
	shared_ptr<int>  p(new int);
	*= 100;
	cout << *p.get() << "  " << *<< endl;
	return 0;
}
100  100

weak_ptr(弱引用智能指针)

弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 * 和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在。

初始化

#include <iostream>
#include <memory>
using namespace std;

int main()
{
	shared_ptr<int> sp(new int);

        //weak_ptr<int> wp1; 构造了一个空 weak_ptr 对象
	weak_ptr<int> wp1; 

        // weak_ptr<int> wp2(wp1); 通过一个空 weak_ptr 对象构造了另一个空 weak_ptr 对象
        weak_ptr<int> wp2(wp1);
        
        //weak_ptr<int> wp3(sp); 通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象
	weak_ptr<int> wp3(sp);

        //wp4 = sp; 通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象(这是一个隐式类型转换)
	weak_ptr<int> wp4;
	wp4 = sp;

        //wp5 = wp3; 通过一个 weak_ptr 对象构造了一个可用的 weak_ptr 实例对象
	weak_ptr<int> wp5;
	wp5 = wp3;

	return 0;
}

use_count()

通过调用 std::weak_ptr 类提供的 use_count() 方法可以获得当前所观测资源的引用计数

#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;

	cout << "use_count: " << endl;
	cout << "wp1: " << wp1.use_count() << endl;
	cout << "wp2: " << wp2.use_count() << endl;
	cout << "wp3: " << wp3.use_count() << endl;
	cout << "wp4: " << wp4.use_count() << endl;
	cout << "wp5: " << wp5.use_count() << endl;
	return 0;
}
    use_count:
    wp1: 0
    wp2: 0
    wp3: 1
    wp4: 1
    wp5: 1

通过打印的结果可以知道,虽然弱引用智能指针 wp3、wp4、wp5 监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了 weak_ptr只是监测资源,并不管理资源。

expired()

通过调用 std::weak_ptr 类提供的 expired() 方法来判断观测的资源是否已经被释放

#include <iostream>
#include <memory>
using namespace std;

int main()
{
	shared_ptr<int> shared(new int(10));
	weak_ptr<int> weak(shared);
	cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

	shared.reset();
	cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

	return 0;
}

1. weak is not expired
2. weak is expired

weak_ptr 监测的就是 shared_ptr 管理的资源,当共享智能指针调用 shared.reset(); 之后管理的资源被释放,因此 weak.expired() 函数的结果返回 true,表示监测的资源已经不存在了。

lock()

通过调用 std::weak_ptr 类提供的 lock() 方法来获取管理所监测资源的 shared_ptr 对象

#include <iostream>
#include <memory>
using namespace std;

int main()
{
	shared_ptr<int> sp1, sp2;
	weak_ptr<int> wp;

	sp1 = std::make_shared<int>(520);
	wp = sp1;
	sp2 = wp.lock();
	cout << "use_count: " << wp.use_count() << endl;

	sp1.reset();
	cout << "use_count: " << wp.use_count() << endl;

	sp1 = wp.lock();
	cout << "use_count: " << wp.use_count() << endl;

	cout << "*sp1: " << *sp1 << endl;
	cout << "*sp2: " << *sp2 << endl;

	return 0;
}

use_count: 2
use_count: 1
use_count: 2
*sp1: 520
*sp2: 520

sp2 = wp.lock(); 通过调用 lock() 方法得到一个用于管理 weak_ptr 对象所监测的资源的共享智能指针对象,使用这个对象初始化 sp2,此时所监测资源的引用计数为 2.

sp1.reset(); 共享智能指针 sp1 被重置,weak_ptr 对象所监测的资源的引用计数减 1.

sp1 = wp.lock(); sp1 重新被初始化,并且管理的还是 weak_ptr 对象所监测的资源,因此引用计数加 1.

共享智能指针对象 sp1 和 sp2 管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是 520.

返回管理this的 share_ptr

#include <iostream>
#include <memory>
using namespace std;

struct Test
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_ptr<Test>(this);
	}

	~Test()
	{
		cout << "析构函数" << endl;
	}
};

int main()
{
	shared_ptr<Test> sp1(new Test);
	cout << "引用个数 " << sp1.use_count() << endl;
	shared_ptr<Test> sp2 = sp1->getSharedPtr();
	cout << "引用个数: " << sp1.use_count() << endl;
	return 0;
}

引用计数: 1
引用计数: 1
析构函数
析构函数

通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针 this 构造了两个智能指针对象 sp1 和 sp2,这二者之间是没有任何关系的,因为 sp2 并不是通过 sp1 初始化得到的实例对象。在离开作用域之后 this 将被构造的两个智能指针各自析构,导致重复析构的错误。

#include <iostream>
#include <memory>
using namespace std;

struct Test : public enable_shared_from_this<Test>
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_from_this();
	}
	~Test()
	{
		cout << "析构函数" << endl;
	}
};

int main()
{
	shared_ptr<Test> sp1(new Test);
	cout << "引用个数: " << sp1.use_count() << endl;
	shared_ptr<Test> sp2 = sp1;
	cout << "引用个数: " << sp1.use_count() << endl;
	return 0;
}

引用个数: 1
引用个数: 2
析构函数

循环引用问题

#include <iostream>
#include <memory>
using namespace std;

class A;
class B;

class A
{
public:
	shared_ptr<B> bptr;
	~A()
	{
		cout << "class TA is disstruct ..." << endl;
	}
};

class B
{
public:
	shared_ptr<A> aptr;
	~B()
	{
		cout << "class TB is disstruct ..." << endl;
	}
};

void testPtr()
{
	shared_ptr<A> ap(new A);
	shared_ptr<B> bp(new B);
	cout << "A 的 引用计数: " << ap.use_count() << endl;
	cout << "B 的 引用计数: " << bp.use_count() << endl;

	ap->bptr = bp;
	bp->aptr = ap;
	cout << "A 的 引用计数: " << ap.use_count() << endl;
	cout << "B 的 引用计数: " << bp.use_count() << endl;
}

int main()
{
	testPtr();
	return 0;
}

A 的 引用计数: 1
B 的 引用计数: 1
A 的 引用计数: 2
B 的 引用计数: 2

共享智能指针 ap、bp 对 A、B 实例对象的引用计数变为 2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类 A、B 的实例对象不能被析构,最终造成内存泄露。通过使用 weak_ptr 可以解决这个问题,只需要将类 A 或者 B 的任意一个成员改为 weak_ptr.

#include <iostream>
#include <memory>
using namespace std;

class A;
class B;

class A
{
public:
	weak_ptr<B> bptr;
	~A()
	{
		cout << "A 的 析构函数" << endl;
	}
};

class B
{
public:
	shared_ptr<A> aptr;
	~B()
	{
		cout << "B 的 析构函数" << endl;
	}
};

void testPtr()
{
	shared_ptr<A> ap(new A);
	shared_ptr<B> bp(new B);
	cout << "A 的 引用计数: " << ap.use_count() << endl;
	cout << "B 的 引用计数: " << bp.use_count() << endl;

	ap->bptr = bp;
	bp->aptr = ap;
	cout << "A  的 引用计数: " << ap.use_count() << endl;
	cout << "B  的 引用计数: " << bp.use_count() << endl;
}

int main()
{
	testPtr();
	return 0;
}

A 的 引用计数: 1
B 的 引用计数: 1
A  的 引用计数: 2
B  的 引用计数: 1
B 的 析构函数
A 的 析构函数

上面程序中,在对类 A 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr 类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域之后 bp 的引用计数减为 0,类 B 的实例对象被析构。

在类 B 的实例对象被析构的时候,内部的 aptr 也被析构,其对 A 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 A 对象的管理也解除了,内存的引用计数减为 0,类 A 的实例对象被析构。

unique_ptr(独占智能指针)

初始化

std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。

#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
	// 通过构造函数初始化对象
	unique_ptr<int> ptr1(new int(10));
	//  报错
	unique_ptr<int> ptr2 = ptr1;
	return 0;
}

unique_ptr不允许被复制,但是可以通过函数返回给其他的unique_ptr,还可以通过std::move()转移给其他的unique_ptr。还是一个unique_ptr独占一个地址。

使用 reset 方法可以让 unique_ptr 解除对原始内存的管理,也可以用来初始化一个独占的智能指针。

#include <iostream>
#include <memory>
using namespace std;
int main()
{
	unique_ptr<int> ptr1(new int(10));
	unique_ptr<int> ptr2 ;
	ptr1.reset(); //解除对原始内存的管理
	ptr2.reset(new int(250)); //重新指定智能指针管理的原始内存

	return 0;
}

如果想要获取独占智能指针管理的原始地址,可以调用 get () 方法

#include <iostream>
#include <memory>
using namespace std;
int main()
{
	unique_ptr<int> ptr1(new int(10));
	unique_ptr<int> ptr2 = move(ptr1);

	ptr2.reset(new int(250));
	cout << *ptr2.get() << endl;	// 得到内存地址中存储的实际数值 250
	return 0;
}
    250

share_ptr实现

#pragma once
#include<iostream>
using namespace std;

template<class T>
class Ref
{
	int r_count = 0;//当前资源计数
	 T* object = nullptr; //被管理的堆区内存
public:
	Ref(*target) :object(target)
	{
		r_count++;
	}

	//引用计数加1
	inline void increase()
	{
		r_count++;
	}

	//引用计数减一并且判断是否要释放掉原始堆区内存
	inline void reduce()
	{
		r_count--;  //引用计数减一
		if (r_count == 0) //如果引用计数减为0释放被管理的堆区内存和自己
		{
			delete object;
			delete this;
		}
	}
*get()
	{
		return object;
	}

	int getCount()
	{
		return r_count;
	}
};

//共享智能指针需要的方法:
/*
		无参构造,传递指针构造,拷贝构造,移动构造,拷贝赋值,移动赋值
		reset()替换对象 reset()销毁对象
		operator*()  operator->()
		get()获取原始指针
		use_count 获得引用计数
*/
template<class T>
class Share_ptr
{
	Ref<T> * ref = nullptr;
public:
	Share_ptr() = default;
	~Share_ptr()
	{
		if (ref) ref->reduce(); //引用计数减一
	}
	Share_ptr(*newP)
	{
		cout << "---------------------调用构造函数-----------------" << endl;
		ref = new Ref<T>(newP);
	}
	Share_ptr(const Share_ptr &other)
	{
		cout << "------------------调用拷贝构造-----------------" << endl;
		this->ref = other.ref;
		if(ref) ref->increase(); //引用计数加1
	}

	Share_ptr( Share_ptr &&other)
	{
		cout << "------------------调用移动构造-----------------"<<endl;
		ref = other.ref;
		other.ref = nullptr;
	}
	Share_ptr& operator=(const Share_ptr &other)
	{
		cout << "-------------------------调用赋值函数-----------------------"<<endl;
		if (ref)
		{
			ref->reduce();
		}
		ref = other.ref;
		if (ref)
		{
			ref->increase();
		}
		return *this;
	}
	Share_ptr& operator =(Share_ptr &&other)
	{
		cout << "--------------------------调用移动赋值------------------------"<<endl;
		if (ref)
		{
			ref->reduce();
		}
		ref = other.ref;
		other.ref = nullptr;
		return *this;
	}

	void reset(T* target)
	{
		if (ref) ref->reduce();
		ref = new Ref<T>(target);
	}
	void reset()
	{
		if (ref) ref->reduce();
		ref = nullptr;
	}
	T& operator*()
	{
		return *ref->get();
	}
	T* operator->()
	{
		if (ref)
		{  
			return ref->get();
		}
	}
	int use_count()
	{
		if (ref) return ref->getCount();
		else return 0;
	}
};

share_ptr实现二

#include<iostream>
using namespace std;

template<typename T>
class M_shared_ptr {
public:
	// 构造函数
	M_shared_ptr(T* ptr = nullptr) {
		if (ptr) {
			count_ = new size_t(1);
			data_ = ptr;
		}
	}
	//移动构造
	M_shared_ptr( M_shared_ptr<T>&& other) {
		count_ = other.count_;
		data_ = other.data_;
		other.count_ = nullptr;
		other.data_ = nullptr;
	}

	// 拷贝构造函数
	M_shared_ptr(const M_shared_ptr<T>& other) {
		count_ = other.count_;
		data_ = other.data_;
		if (count_) {
			++(*count_);
		}
	}

	// 析构函数
	~M_shared_ptr() {
		release();
	}

	// 赋值运算符
	M_shared_ptr<T>& operator=(const M_shared_ptr<T>& other) {
		if (this != &other) {
			this->release();
			count_ = other.count_;
			data_ = other.data_;
			if (count_) {
				++(*count_);
			}
		}
		return *this;
	}

	// 解引用操作符
	T& operator*() const {
		return *data_;
	}

	// 成员访问操作符
	T* operator->() const {
		return data_;
	}

	// 获取引用计数
	size_t use_count() const {
		return count_ ? *count_ : 0;
	}

	// 重置智能指针
	void reset(T* ptr = nullptr) {
		this->release();
		if (ptr) {
			count_ = new size_t(1);
			data_ = ptr;
		}
	}

private:
	// 释放资源
	void release() {
		if (count_) {
			--(*count_);
			if (*count_ == 0) {
				delete count_;
				delete data_;
			}
			count_ = nullptr;
			data_ = nullptr;
		}
	}

private:
	T* data_ = nullptr;
	size_t* count_ = nullptr;
};
int main()
{
	M_shared_ptr<int>q;
	M_shared_ptr<int>p(new int(3));
	cout << p.use_count() << endl;
	M_shared_ptr<int>p1 = move(p);
	cout << p1.use_count() << endl;
= p;
	cout << q.use_count() << endl;
}

static_cast

用法:

用法为 static_cast<type-id> (expression)该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。

使用场景:

● 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。

● 用于基本数据类型之间的转换,如把int转换成char。这种转换的安全性也要开发人员来保证。

● 把空指针转换成目标类型的空指针。

● 把任何类型的表达式转换成void类型。

#include<iostream>
using namespace std;

int main()
{
	char a = 'a';
	int b = static_cast<int>(a);//正确,将char型数据转换成int型数据

	double *= new double;
	void *= static_cast<void*>(c);//正确,将double指针转换成void指针

	int e = 10;
	const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据

	const int g = 20;
	int *= static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
}
#include<iostream>
using namespace std;
class father{};
class son :public father{};
int main()
{
	father *= new father;
	son *= static_cast<son*>(f);// 不安全

	son *s1 = new son;
	father *f1 = static_cast<father*>(s1); //安全
}

const_cast

用法:

const_cast用于强制去掉不能被修改的常数特性,但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用

使用场景:

 常量指针被转化成非常量指针,并且仍然指向原来的对象;

    常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

#include<iostream>
using namespace std;

int main()
{
	const double a = 7;
	const double* p = &a;
	double* q = const_cast<double*>(p);
	*= 20; 
	cout << *<< endl; // 20
}

reinterpret_cast

在C++语言中,reinterpret_cast主要有三种强制转换用途:改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型。

用法:

reinterpret_cast<type_id> (expression)type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。

    它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。在使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝,因此在使用过程中需要特别谨慎!

dynamic_cast

用法:

dynamic_cast<type_id> (expression)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。不能用于内置的基本数据类型的强制转换。

dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL。

使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过。需要检测有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。

在类的转换时,在类层次间进行上行转换时,dynamic_cast和的效果是一样的。在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白要躺平

谢谢您的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值