先说一句为什么要用智能指针。智能指针是为了解决内存泄露的问题。C/C++程序员可以自己开辟堆(heap)上的内存空间,同时也需要自己释放堆上的内存空间。一旦忘记释放内存空间,这样会造成内存泄露。
不得不提及一下RAII机制(资源获取即初始化,Resource Acquisition Is Initialization)。在类的构造函数里面申请资源,然后使用,最后在析构函数中释放资源。所以这也就为什么析构函数需要用virtual来修饰来避免出现内存泄露。
计算机有栈和堆两种。
这里提及一点。C++中的对象(class)是指所有的内置型对象(int、float、double等和外置型(用户自定义的对象)对象。一切皆为对象。
如果在栈上创建相应的class,那么OS会自动释放掉相应的内存空间,所以RAII机制工作正常,离开相应的作用域时,class会自动调用自己的析构函数释放资源。但是如果采用new方式在堆上创建class,那么class不会调用自己的析构函数。程序员必须使用delete去销毁它。否则就会造成内存泄露。
这里再插入两个小知识,我以前面试腾讯外包时被问到的点。
-
new、delete的机制。
-
new会抛出异常以及如何让new不抛异常。
1.new有两个步骤:(1)调用operator new()函数开辟内存空间。(2)调用class的构造函数。
delete也有两个步骤:(1)调用class的析构函数。(2)调用operator delete()释放内存。
2.new会抛出异常。为了防止抛出异常。就应该使用 std::nothrow。例:
char *ptr = new(std::nothrow) char [20];
开始介绍智能指针,他们都包含在头文件#include <memory>中。主要解决了部分获取资源自动释放的问题。
-
unique_ptr
独占式智能指针,不允许拷贝复制,不允许拷贝构造。它不仅能代理new创建的单个对象,也能够代理new[]创建的对象数组。
代码如下:
///
/// @file unique.cpp
/// @author kogan([email protected])
/// @date 2020-06-21 09:28:02
///
#include <iostream>
#include <memory>
#include <utility>
using std::cout;
using std::endl;
int main()
{
//make_unique在C++11中被遗漏了,在C++14中添加。所以要使用-std=c++14
//创建int型指针。
//使用工厂方法创建
std::unique_ptr<int> ptr = std::make_unique<int>(5);
//也可以初始化时直接创建
std::unique_ptr<int> ptr2(new int(6));
//std::unique_ptr<int> ptr3(ptr); //不允许拷贝构造
//std::unique_ptr<int> ptr3 = ptr;//不允许赋值
//创建int型动态数组
std::unique_ptr<int[]> ptrArray = std::make_unique<int[]>(20);
cout << "*ptr: " << *ptr << endl;
//创建出来的unique动态数组,可以使用下表访问。
ptrArray[2] = 3;
cout << ptrArray[2] << endl;
//错误用法,能编译成功,但是会出问题。
//std::unique_ptr<int> ptr5(new int[20]);
return 0;
}
-
shared_ptr
引用计数型智能指针。是最有价值、最重要、最有用的组成部分。可以自由地拷贝和赋值。当这个指针指向某个指针时,会引用计数加一。如果是从另外一个smart_ptr那里获取某指针的管理权,则两个smart_ptr指针都会引用计数加一。当引用计数为0时,smart_ptr会删除指针所指向的内存空间。
代码如下:
///
/// @file shared.cpp
/// @author kogan([email protected])
/// @date 2020-06-21 11:51:39
///
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
int main()
{
//初始化方式
std::shared_ptr<int> ptr(new int(3));
std::shared_ptr<int> ptr2 = std::make_shared<int>(5);
//将ptr2管理的指针赋值给ptr3
std::shared_ptr<int> ptr3(ptr2);
cout << ptr3.use_count() << endl;
return 0;
}
面试重点:循环引用计数。
循环引用计数:两个smart_ptr指针互相指向对方,造成内存泄露。此时需要使用weak_ptr。
代码如下:
-
weak_ptr
弱引用指针。用于观察shared_ptr或weak_ptr。用于解决循环引用计数。因为weak_ptr没有共享资源,它的构造函数不会引起指针引用计数的变化。
///
/// @file circle_reference.cpp
/// @author kogan([email protected])
/// @date 2020-06-21 12:04:48
///
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
using std::shared_ptr;
class Child;//前置声明
class Parent
{
public:
Parent(){ cout << "Parent()" << endl; }
~Parent() { cout << "~Parent()" << endl; }
shared_ptr<Child> m_Child;
};
class Child
{
public:
Child(){ cout << "Child()" << endl; }
~Child(){ cout << "~Child()" << endl; }
shared_ptr<Parent> m_Parent;
};
int main(void)
{
//shared_ptr的循环引用会导致内存泄漏
shared_ptr<Parent> parent(new Parent);
shared_ptr<Child> child(new Child);
cout << "sizeof(shared_ptr) = " << sizeof(parent) << endl;
cout << "parent 's use_count() = " << parent.use_count() << endl;
cout << "child's use_count() = " << child.use_count() << endl;
parent->m_Child = child;//赋值
child->m_Parent = parent;
cout << "parent 's use_count() = " << parent.use_count() << endl;
cout << "child's use_count() = " << child.use_count() << endl;
return 0;
}
使用weak_ptr后不会发生内存泄露。
///
/// @file circle_reference2.cpp
/// @author kogan([email protected])
/// @date 2020-06-21 12:09:37
///
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
using std::shared_ptr;
using std::weak_ptr;
class Child;//前置声明
class Parent
{
public:
Parent(){ cout << "Parent()" << endl; }
~Parent() { cout << "~Parent()" << endl; }
shared_ptr<Child> m_Child;
};
class Child
{
public:
Child(){ cout << "Child()" << endl; }
~Child(){ cout << "~Child()" << endl; }
//shared_ptr<Parent> m_Parent;
weak_ptr<Parent> m_Parent;
};
int main(void)
{
//shared_ptr的循环引用会导致内存泄漏
//
//解决方案是将其中一个指针设置为weak_ptr
//weak_ptr做赋值操作的时候,不会改变引用计数的值
shared_ptr<Parent> parent(new Parent);
shared_ptr<Child> child(new Child);
cout << "sizeof(shared_ptr) = " << sizeof(parent) << endl;
cout << "parent 's use_count() = " << parent.use_count() << endl;
cout << "child's use_count() = " << child.use_count() << endl;
parent->m_Child = child;//赋值
child->m_Parent = parent;// weak_ptr = shared_ptr;
cout << endl << "执行赋值之后:" << endl;
cout << "parent 's use_count() = " << parent.use_count() << endl;
cout << "child's use_count() = " << child.use_count() << endl;
return 0;
}
另一种循环引用计数:
closs node
{
public:
typedef shared_ptr<Node> ptr_type;
ptr_type next;
};
auto p1 = make_shared_ptr<Node>();
auto p2 = make_shared_ptr<Node>();
weak的lock函数,在某个作用于内使用lock()会返回一个shared_ptr。
该对象可以操作weak_ptr所指向的shared_ptr。
///
/// @file shared.cpp
/// @author kogan([email protected])
/// @date 2020-06-21 11:51:39
///
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
int main()
{
//初始化方式
std::shared_ptr<int> ptr(new int(3));
std::shared_ptr<int> ptr2 = std::make_shared<int>(5);
//将ptr2管理的指针赋值给ptr3
std::shared_ptr<int> ptr3(ptr2);
cout << "reference count: " << ptr3.use_count() << endl;
std::weak_ptr<int> wptr(ptr);
cout << "reference count: " << ptr3.use_count() << endl;
{
cout << "*ptr: " << *ptr << endl;
std::weak_ptr<int> wptr(ptr);
auto sp = wptr.lock();
(*sp) = 5;
cout << "*ptr: " << *ptr << endl;
}
cout << "*ptr: " << *ptr << endl;
return 0;
}