C++11/14標準(二)智能指針

先說一句爲什麼要用智能指針。智能指針是爲了解決內存泄露的問題。C/C++程序員可以自己開闢堆(heap)上的內存空間,同時也需要自己釋放堆上的內存空間。一旦忘記釋放內存空間,這樣會造成內存泄露。

    不得不提及一下RAII機制(資源獲取即初始化,Resource Acquisition Is Initialization)。在類的構造函數裏面申請資源,然後使用,最後在析構函數中釋放資源。所以這也就爲什麼析構函數需要用virtual來修飾來避免出現內存泄露。

    計算機有棧和堆兩種。

    這裏提及一點。C++中的對象(class)是指所有的內置型對象(int、float、double等和外置型(用戶自定義的對象)對象。一切皆爲對象。

如果在棧上創建相應的class,那麼OS會自動釋放掉相應的內存空間,所以RAII機制工作正常,離開相應的作用域時,class會自動調用自己的析構函數釋放資源。但是如果採用new方式在堆上創建class,那麼class不會調用自己的析構函數。程序員必須使用delete去銷燬它。否則就會造成內存泄露。

    這裏再插入兩個小知識,我以前面試騰訊外包時被問到的點。

  1. new、delete的機制。

  2. 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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章