C++11的智能指针

1,084 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的两种智能指针的区别在于管理底层指针的方法不同,shared_ptr允许多个指针指向同一个对象,unique_ptr则 独占 所指向的对象。标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向 shared_ptr 所管理的对象,这三种智能指针都定义在memory头文件中。

1.auto_ptr

auto_ptr的赋值和拷贝构造存在于一个管理权转移的操作,也就是说将赋值和拷贝构造结束后,就只会有一个指针拥有管理权,另外的一个指针就会指向NULL,这样就很容易造成NULL指针的解引用,从而导致内存崩溃。

下面给一个例子去说明auto_ptr的缺点:

class Dog {
public:
	Dog(int _age = 0) {
		age = _age;
		cout << "Dog: " << age << endl;
	}
	~Dog() {
		cout << "~Dog: " << age << endl;
	}
	void sound() {
		cout << "wang wang " << endl;
	}
	int age;
};


int main()
{
	auto_ptr<Dog>dog1(new Dog());
	dog1->sound();
	auto_ptr<Dog>dog2;
	dog2 = dog1;//复制旧的dog1给dog2
	dog2->sound();//输出信息,复制成功
	dog1->sound();//崩溃
	return 0;
}

image.png

经过检查,罪魁祸首是“dog2 = dog1;,这行代码,dog2完全夺取了dog1的内存管理所有权,导致 my_memory 悬空,最后使用时导致崩溃。所以,我们需要找到新的方法去解决这个问题。就有了unique_ptr

 2.unique_ptr

        unique_ptr 由 C++11 引入,旨在替代不安全的 auto_ptr。它持有对对象的独有权——两个unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。

它将拷贝构造函数和赋值运算符重载函数设置为了私有 并且只声明不实现,这种做法简单而粗暴,但是却很好的防止了别人在类外进行拷贝和赋值,提高了代码的安全性。

image.png

 3.shared_ptr

shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象。shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:

(1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;

(2)时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样;

(3)开销并不是我们不使用 shared_ptr 的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。

class Dog {
public:
	Dog(int _age = 0) {
		age = _age;
		cout << "Dog: " << age << endl;
	}
	~Dog() {
		cout << "~Dog: " << age << endl;
	}
	void sound() {
		cout << "wang wang " << endl;
	}
	int age;
	shared_ptr<Dog>dog1;
	shared_ptr<Dog>dog2;
};


int main()
{
	shared_ptr<Dog>a(new Dog());
	shared_ptr<Dog>b(new Dog());
	cout << a.use_count() << endl;
	cout << b.use_count() << endl;
	a->dog1 = b;
	b->dog2 = a;
	cout << a.use_count() << endl;
	cout << b.use_count() << endl;
	return 0;
}

从显示的结果来看,构造的a和b在程序结束时都没有被析构,从而造成了内存泄漏。

4.weak_ptr

    weak_ptr是对share_ptr的补充。是为了避免share_ptr的循环引用问题。

    share_ptr 成功在它的应用计数,但是它的缺陷也在于引用计数,例如在双向链表里,当只有两个节点时,cur的prev指针指向NULL,next节点的next指针执行NULL,如果实现为share_ptr的话就会出现循环引用的问题。