C 是我平時的工作中用的最多的語言,Python基本是在學習的時候會用,有時候也會用它來寫一寫腳本。所以,今天準備摻一點C 的知識。
智能指針是C 11标準中的其中一個特性。本文可能需要有一點C 語言的基礎。不過盡量用簡潔的文字來介紹。如果對C 語言不了解又想學習的話,需要C 學習資料的後台私聊我哦,都是我之前自己學習整理出來的資料。感覺還可以。
在開發C 程序的時候,我們使用new動态的從堆中申請内存,然後使用delete将這段内存釋放。使用new申請的内存C 編譯器是不會自動釋放的。因此,如果我們使用了new來申請内存,但是沒有使用delete釋放内存,就會造成内存洩漏。如果申請内存的操作是在一個循環中的話,就會不斷的造成内存洩漏,最終導緻内存不足,程序崩潰。這是很嚴重的問題。
顯然,讓程序員來管理内存的釋放問題是很繁瑣的。有的時候,我們甚至不知道應該在什麼時候使用delete來釋放内存。比如說在編寫比較複雜的多線程程序的時候,申請的内存可能會有多個線程同時訪問,可能你自己都無法确定應該合适釋放這一塊内存。因此,如果能讓C 編譯器來自動完成内存的分配和釋放,那程序員的壓力就小很多了。
智能指針内存的分配和釋放都是由C 編譯器自動完成的。這就是智能指針存在的意義,我們可以将繁瑣的内存管理問題交給C 編譯器,而将精力放在我們的業務邏輯上。
智能指針的類型
C 11中提出的智能指針有三種類型:shared_ptr、unique_ptr、weak_ptr。使用這三種智能指針的時候需要包含庫memory。
(1) shared_ptr
shared_ptr(就是一種指針)管理内存的機制如下:shared_ptr采用引用計數的方式來管理所指向的對象。什麼意思呢?舉個例子:
現在有一個對象dog,有一個shared_ptr指向它, 此時它的引用計數為1;當有一個新的shared_ptr也指向了dog,那麼它的引用計數自動加1,為1;當指向了dog的shared_ptr了離開了它的作用域,引用計數減1,又變為1了。當引用技術為0時(也就是說所有指向dog的shared_ptr都離開了作用域),dog占用的内存自動釋放。
還不理解?沒關系,看一段代碼:
#include
#include
#include
class Dog {
private:
std::string name_;
public:
Dog(std::string name) {
std::cout << "Dog is created." << name << std::endl;
name_ = name;
}
Dog() {
std::cout << "Nameless dog created." << std::endl;
name_ = "nameless";
}
~Dog() {
std::cout << "dog is destroyed: " << name_ << std::endl;
}
void bark() {
std::cout << "Dog " << name_ << " rules" << std::endl;
}
};
void foo()
{
//創建一個指針下面兩種方式都可以
//shared_ptr p(new Dog("Gunner"));
std::shared_ptr p = std::make_shared("Gunner");
//p.use_count==1
std::cout << "p->use_count() = " << p.use_count() << std::endl;
{
std::shared_ptr p2 = p;
//p.use_count==2
std::cout << "p->use_count() = " << p.use_count() << std::endl;
p2->bark();
} //離開大括号時,p2的作用域結束,p的引用計數減1
//p.use_count==1
std::cout << "p->use_count() = " << p.use_count() << std::endl;
p->bark();
}
int main()
{
foo();
}
首先要注意下面幾點:
- 創建shared_ptr的方式有兩種
- 直接使用new關鍵字的方式: shared_ptr p(new Dog("Gunner"));
- 使用make_shared的方式:shared_ptr p = make_shared("Gunner");
- shared_ptr、make_shared都是在命名空間std當中,為了避免初學者誤會,我直接寫成了std::shared_ptr、std::make_shared的方式,而沒有使用using namespace std;
運行結果如下:
怎麼理解内存自動釋放了呢: 在foo()函數執行結束之後,智能指針p離開了作用域,它的引用計數減為0了,然後創建的Dog的對象的析構函數自動調用了,輸出: dog is destroyed: Gunner。
上面有幾個C 中的重要概念,稍微做一些解釋:
- 命名空間:命名空間也稱為名字空間,最通俗的理解就是一個命名的容器,一個空間内的變量、函數、類等的命名不可以相同,但是不同空間的命名可以相同。std是C 編譯器的命名空間,C 标準庫中的函數或者對象都是在命名空間std中定義的,所以我們要使用标準函數庫中的函數或對象都要使用std來限定。
- 析構函數: 析構函數和構造函數可以認為是一對函數。構造函數在創建一個類的對象時被自動調用,通常用來做一些初始化的工作。析構函數與構造函數相反,當對象結束其生命周期,如對象離開它的作用域,系統自動執行析構函數。析構函數往往用來做“清理善後” 的工作(例如在建立對象時用new開辟了一片内存空間,delete會自動調用析構函數後釋放内存)。
(2) unique_ptr
unique是獨一無二的意思。unique_ptr的涵義也是相似的,它表達的是一種獨占的思想,與shared_ptr最大的區别是unique_ptr不共享它的指針,某個時刻隻能有一個unique_ptr指向一個給定的對象。
創建unique_ptr的方式如下:
- 使用new關鍵字:std::unique_ptr ptr(new Example(1));
- 使用std::make_unique:std::unique_ptr ptr = std::make_unique(1);
常用的函數說明:
- get() : 返回被管理對象的指針
- release() : 返回指向被管理對象的指針,并釋放所有權
- swap() : 交換被管理對象
使用示例:
#include
#include
#include
using namespace std;
class Example {
public:
Example(int param = 0) {
number = param;
cout << "Example: " << number << endl;
}
~Example() {
cout << "~Example: " << number << endl;
}
void test_print() {
cout << "in test print: number = " << number << endl;
}
void set_number(int num) {
number = num;
}
private:
int number;
};
void test1() {
unique_ptr ptr1 = make_unique(1);
if (ptr1.get()) {
ptr1.get()->test_print();
ptr1->set_number(2);
(*ptr1).test_print();
}
unique_ptr ptr2(new Example(20));
ptr2->test_print();
ptr1.swap(ptr2);
cout << "ptr1和ptr2交換管理對象" << endl;
ptr1->test_print();
ptr2->test_print();
}
int main() {
test1();
return 0;
}
運行結果:
(3) weak_ptr
std::weak_ptr是一種智能指針。它對被std::shared_ptr管理的對象存在非擁有性(弱)引用。weak_ptr是為了配合shared_ptr而引入的一種智能指針,它不具有普通指針的行為,沒有重載運算符*和->,其最大作用在于協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。weak_ptr可以從一個shared_ptr或者另weak_ptr對象構造,獲得資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引起指針引用計數的增加。
使用weak_ptr的成員函數use_count()可以觀測資源的引用計數,另一個成員函數expired()的功能等價于使得use_count==0,表示被觀測的資源(也就是shared_ptr管理的資源)已經不複存在。weak_ptr有一個重要的成員函數lock()可以從被觀測的shared_ptr中獲得一個可用的shared_ptr對象,從而操作資源。
weak_ptr被設計用來避免std::shared_ptr的循環引用。
什麼是循環引用問題,下面舉個例子說明一下:
假設現在有兩個類A、B,創建了兩個智能指針shared_ptr ptr_A、shared_ptr ptr_B分别指向了A、B兩個類的對象a、b。A中有個shared_ptr指向b,B中有個shared_ptr指向a。
下面我們看一下ptr_A、ptr_B的引用計數分别是多少:
- ptr_A.use_count = 2
- ptr_B.use_count = 2
然後程序結束時,ptr_A、ptr_B都離開了它的作用域,引用計數減為1,所以a、b占用的内存不會釋放。這就是shared_ptr的缺陷。
下面可以從一個例子中看一下:
#include
#include
class foo;
class Test {
public:
Test() {
std::cout << "construct.." << std::endl;
}
void method() {
std::cout << "welcome Test.." << std::endl;
}
~Test() {
std::cout << "destruct.." << std::endl;
}
public:
std::shared_ptr fooptr;
};
class foo {
public:
foo() {
std::cout << "foo construct.." << std::endl;
}
void method() {
std::cout << "welcome Test foo.." << std::endl;
}
~foo() {
std::cout << "foo destruct.." << std::endl;
}
public:
std::shared_ptr testptr;
};
int main()
{
// 循環引用 測試
Test* t2 = new Test();
foo* foo1 = new foo();
std::shared_ptr shptr_Test(t2);
std::shared_ptr shptr_foo(foo1);
std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
shptr_Test->fooptr = shptr_foo;
shptr_foo->testptr = shptr_Test;
std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
return 0;
}
運行結果如下:
在程序結束時,Test類和foo類的析構函數并沒有調用。
使用weak_ptr改進的程序如下:
#include
#include
class foo;
class Test {
public:
Test() {
std::cout << "construct.." << std::endl;
}
void method() {
std::cout << "welcome Test.." << std::endl;
}
~Test() {
std::cout << "destruct.." << std::endl;
}
public:
std::weak_ptr fooptr;
};
class foo {
public:
foo() {
std::cout << "foo construct.." << std::endl;
}
void method() {
std::cout << "welcome Test foo.." << std::endl;
}
~foo() {
std::cout << "foo destruct.." << std::endl;
}
public:
std::weak_ptr testptr;
};
int main() {
// 循環引用 測試
Test* t2 = new Test();
foo* foo1 = new foo();
std::shared_ptr shptr_Test(t2);
std::shared_ptr shptr_foo(foo1);
std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
shptr_Test->fooptr = shptr_foo;
shptr_foo->testptr = shptr_Test;
std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
return 0;
}
運行結果如下:
可以看到析構函數自動調用了,内存正常釋放。
今天的内容就到這兒了。如果對我的推、文有興趣,歡迎轉、載分、享。也可以推薦給朋友關注哦。隻推幹貨,甯缺毋濫。
, 更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!