G
N
I
D
A
O
L

C++智能指针

C++智能指针

在《Effective C++》的条款54条,是这样描述智能指针的,用好它,便能有效解决内存管理、泄露的问题。


智能指针(smart pointers):嵌套在tr1命名空间内,TR1组件shared_ptr的全名是std::tr1::shared_ptr,使用时std::shared_ptr即可.

tr1::shared_ptrtr1::weak_ptr。前者的作用有如内置指针,但会记录有多少个tr1::shared_ptrs共同指向同一个对象。这便是所谓的reference couming(引用计数)。一旦最后一个这样的指针被销毁,也就是一旦某对象的引用次数变成 0,这个对象会被自动删除。这在非环形(acyclic)数据结构中防止资源泄漏很有帮助,但如果两个或多个对象内含tr1::shared_ptrs并形成环状(cycle),这个环形会造成每个对象的引用次数都超过 0——即使指向这个环形的所有指针都已被销毁(也就是这一群对象整体看来己无法触及)。这就是为什么又有个tr1::weak_ptr的原因。tr1::weak_ptr的设计使其表现像是"非环形tr1::shared_ptr-based数据结构"中的环形感生指针(cycle-inducing pointers)。tr1::weak_ptr并不参与引用计数的计算∶当最后一个指向某对象的 tr1::shared_ptr被销毁,纵使还有个trl∶∶weak_ptrs继续指向同一对象,该对象仍旧会被删除。这种情况下的tr1∶∶weak_ptrs会被自动标示无效,tr1∶∶shared_ptr或许是拥有最广泛用途的 TR1 组件

在《C++ Primer Plus》中指出“智能指针是行为类似于指针的类对象”。其存在的最重要的一点就是可帮助管理动态内存分配的智能指针模板。原理就是“析构函数的应用”。当一个对象过期时,让他的析构函数删除指向的内存即可。

  • auto_ptr(C++98的方案,c++11已经摒弃该指针,auto_ptr最大的弊端在于允许很多几乎没有实际用处又不符合常规认知的行为,用scoped_ptr和unique_ptr可以利用静态检查早早地指出这些无意义操作的存在。文中会略谈)
  • unique_ptr
  • shared_ptr

为什么要使用智能指针?

答:因为内存管理是一件非常麻烦的事,在开发过程中会经常性的因为内存泄露问题出现严重的后果。如下所示:

void remodel() {
 double *ps = new double;
 *ps = 25.5;
 return;
}

上述代码的问题就是进入remodel()后申请了堆内存*ps,但是return的时候又没有释放改内存空间,因为很可能会出现程序错误,当然即使你使用了delete ps;,也不一定就能让程序不出现该类错误。

void remodel() {
 double *ps = new double;
 *ps = 25.5;
 if(weird_thing())
     throw exception();
 delete ps;
 return;
}

如上,在抛出异常的时候,依然会出现堆内存未释放的问题。

使用

///****************************************************************************
/// @brief   : 创建一个分配堆内存的函数,但是无释放空间操作
///****************************************************************************
void demo1() {
	double *pd = new double;
	*pd = 25;
	return; //删除pd,值被保留在动态内存中
}
///****************************************************************************
/// @brief   : 创建一个分配堆内存的函数,调用auto_ptr管理内存空间
///****************************************************************************
void demo2() {
	auto_ptr<double> ap(new double);
	*ap = 25.5;
	return; //删除ap,ap的析构函数释放动态内存。
}

智能指针的几种声明与使用方式:

shared_ptr<double> pd;
double *p_reg =new double;
pd = shared<double>(p_reg); //#1 
shared_ptr<double> psahred(p_reg); //#2
shared_ptr<double> sd(new double); //#3

#1:所有智能指针类都有一个explicit构造函数,该函数将指针作为参数。因此不需要将指针转换为智能对象。

关于shared_ptr的详细使用可以看这篇文章📑 shared_ptr使用

如何创建智能指针对象

创建智能指针对象必须包含头文件memory,然后通过模板类实现auot_ptr()的调用与实现。

#include <memory>
#include <string>
template <class X> class auto_ptr {
public:
	/*
		1. explicit 避免隐式转换
		2. throw() 意味着构造函数不会引发异常
	*/
	explicit auto_ptr(X *p = 0) throw();
};

因为使用了模板,因此可以通过使用X类型的auto_ptr来获得指向X类型的auto_ptr

auto_ptr<double> ad(new double); //auto_ptr to double
auto_ptr<string> as(new string); //auto_ptr to string

并且可以使用你的自定义类型,同时还可以对智能指针执行解除引用操作,用它来访问结构成员:

class myType {
private:
	std::string str;
public:
    int date;
	myType() {}
	myType(const std::string s):str(s) {
		std::cout << "create a myType~" << std::endl;
	}
};
auto_ptr<myType> pmt(new myType); //auto_ptr to myType
pmt->date; //访问myType中date成员

为什么摒弃auto_ptr

使用《C++ primer plus》中的一个案例,顺便说明为什么会有shred_ptr,以及与auto_ptr的区别。

auto_ptr<string> tableau(new string("这里有一个小姐姐,单身可撩~"));
auot_ptr<string> rt;
rt = tableau;

上述语句声明了两个变量,tableau和rt,如果这两个变量是常规指针,则两个变量都指向同一个地址,意即在程序结束时或者析构函数调用时,该地址会被删除两次,一次是tableau过期时,一次是rt过期时。解决这种问题的方法有多种,使share_ptr就可以解决这种所有权问题。

  1. 定义赋值运算符,进行深拷贝操作,这样两个指针指向不同的地址。

  2. 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是auto_ptr和unique_ptr的策略。

    unique_ptr<string> tableau(new string("这里有一个小姐姐,单身可撩~")); //声明一个unique_ptr
    unique_ptr<string> rt; 
    rt = tableau; //非法,编译报错
    
  3. 创建智能更高的指针,跟踪引用特定对象的智能指针数,也即引用计数(reference counting)。也就是shared_ptr的实现策略,当引用计数为0时,才会执行删除操作。

    auto_ptr<string> tableau(new string("这里有一个小姐姐,单身可撩~"));
    shared_ptr<string> rt; // 在这一步只需将rt声明为shared_ptr即可
    rt = tableau; //tableau的引用计数变为2
    

    在程序执行末尾,后声明的rt先调用析构函数,引用计数变为1;然后tableau地阿勇析构函数,引用计数变为0,该地址指向的空间被释放。


相比于auto_ptr,unique_ptr还有另外一个优点。就是可以用于数组的变体。在C++中,必须将delete和new配对,将delete[]和new[]配对使用。模板auto——ptr使用delete而不是delete[],因此,只能与new一起使用,而不能与new[]一起使用。但unique_ptr有使用new[]和delete[]的版本。

std::unique_ptr<double[]>pda(new double(5)); //使用delete[]删除

关于容器与智能指针的一个很好的demo:

///****************************************************************************
/// @data    :	2021/1/17
/// @input   :	随机整型变量
/// @output  :	返回一个unique_ptr
/// @brief   :  
///****************************************************************************
unique_ptr<int> make_int(int n) {
	return unique_ptr<int>(new int(n));
}
// 这里必须按引用传值,unique_ptr所有权的问题:因为如果使用值传递,
// 编译器会认为vui[i]不再指向有效的数据,会出现编译错误。
void show(unique_ptr<int> &pi) {
	cout << *pi << " ";
}
int main()
{

	////指向容器的智能指针
	//auto_ptr<vector<int>>  avi(new vector<int>);
	//avi->push_back(5);
	//cout<<"打印容器第一个数:" << avi->front();

	// 存储智能指针的容器
	vector<unique_ptr<int> > vui(10);
	for (size_t i = 0; i < vui.size(); i++)
	{
		vui[i] = make_int(rand() % 1000);
	}
	vui.push_back(make_int(rand() % 1000));

	for_each(vui.begin(),vui.end(),show);

	system("pause");
	return 0;
}

但是,unique_ptr却可以作为右值转换为shared_ptr,还是上边的代码:

unique_ptr<int> pup(make_int(rand() % 1000)); //ok
shared_ptr<int> spp(pup); // not allowed,pup是左值
shared_ptr<int> spr(make_int(rand() % 1000)); //ok
posted @ 2021-10-21 10:33  StimuMing  阅读(69)  评论(0编辑  收藏  举报