先說一句爲什麼要用智能指針。智能指針是爲了解決內存泄露的問題。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;
}