欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

融會貫通C++智能指針教程

 更新時間:2021年08月30日 14:52:22   作者:只會寫bug~  
本文主要介紹了c++的基礎(chǔ)知識,通過不帶引用計數(shù)的只能指針和帶引用的智能指針,shared_ptr和weak_ptr,多線程訪問共享對象的線程安全問題以及自定義刪除器作了詳細的分析解答

一、基礎(chǔ)知識介紹

裸指針常出現(xiàn)以下幾個問題:

  • 忘記釋放資源,導(dǎo)致資源泄露(常發(fā)生內(nèi)存泄漏問題)
  • 同一資源釋放多次,導(dǎo)致釋放野指針,程序崩潰
  • 寫了釋放資源的代碼,但是由于程序邏輯滿足條件,執(zhí)行中間某句代碼時程序就退出了,導(dǎo)致釋放資源的代碼未被執(zhí)行到
  • 代碼運行過程中發(fā)生異常,隨著異常棧展開,導(dǎo)致釋放資源的代碼未被執(zhí)行到
template<typename T> 
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr):_ptr(ptr) {}
    ~SmartPtr() {delete _ptr;}
private:
    T* _ptr;
};
int main(){
    SmartPtr<int> ptr(new int);
    return 0;
}

上面這段代碼就是一個非常簡單的智能指針,主要用到了這兩點:

手動實現(xiàn)智能指針體現(xiàn)在把裸指針進行了面向?qū)ο蟮姆庋b,在構(gòu)造函數(shù)中初始化資源地址,在析構(gòu)函數(shù)中負責(zé)釋放資源

利用棧上的對象出作用域自動析構(gòu)這個特點,在智能指針的析構(gòu)函數(shù)中保證釋放資源。

所以,智能指針一般都是定義在棧上的

面試官:能不能在堆上定義智能指針?
答:不能。就好比SmartPtr<int>* ptr = new SmartPtr<int>();這段代碼中,在堆空間定義一個智能指針,

這依然需要我們手動進行delete,否則無法堆空間的對象無法釋放,因為堆空間的對象無法自動調(diào)用析構(gòu)函數(shù)。

一般而言智能指針還需要提供裸指針常見的*->兩種運算符的重載函數(shù):

    const T& operator*() const{return *_ptr;}
    T& operator*(){return *_ptr;}
    const T operator->() const{return _ptr;}
    T operator->(){return _ptr;}

二、不帶引用計數(shù)的智能指針

int main(){
    SmartPtr<int> ptr1(new int);
    SmartPtr<int> ptr2(ptr1);
    return 0;
}

以上代碼運行時,由于ptr2拷貝構(gòu)造時默認是淺拷貝,會出現(xiàn)同一資源釋放兩次的錯誤(釋放野指針),這里需要解決兩個問題:

智能指針的淺拷貝

多個智能指針指向同一個資源的時候,怎么保證資源只釋放一次,而不是每個智能指針都釋放一次

不帶引用計數(shù)的智能指針主要包括

auto_ptr,scoped_ptrunique_ptr

(1)auto_ptr源碼

auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
_Ty* release() noexcept {
    _Ty* _Tmp = _Myptr;
    _Myptr    = nullptr;
    return _Tmp;
}

使用auto_ptr

	auto_ptr<int> ptr1(new int);
    auto_ptr<int> ptr2(ptr1);

從源碼可以看到,auto_ptr底層先是將ptr1置空,然后將指向的資源再給ptr2, auto_ptr所做的就是使最后一個構(gòu)造的指針指向資源,以前的指針全都置空,如果再去訪問以前的指針就是訪問空指針了,這很危險。所以一般不使用auto_ptr

(2)scoped_ptr

該智能指針底層私有化了拷貝構(gòu)造函數(shù)和operator=賦值函數(shù),

從根本上杜絕了智能指針淺拷貝的發(fā)生,所以scoped_ptr也是不能用在容器當(dāng)中的。

  • 如果容器互相進行拷貝或者賦值,就會引起scoped_ptr對象的拷貝構(gòu)造和賦值,這是不允許的,代碼會提示編譯錯誤。
  • auto_ptrscoped_ptr這一點上的區(qū)別,有些資料上用所有權(quán)的概念來描述,道理是相同的。
  • auto_ptr可以任意轉(zhuǎn)移資源的所有權(quán),而scoped_ptr不會轉(zhuǎn)移所有權(quán)(因為拷貝構(gòu)造和賦值被禁止了)

一般也不推薦使用scoped_ptr

(3)unique_ptr源碼

	template <class _Dx2 = _Dx, enable_if_t<is_move_constructible_v<_Dx2>, int> = 0>
    unique_ptr(unique_ptr&& _Right) noexcept
        : _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx>(_Right.get_deleter()), _Right.release()) {}	
	unique_ptr(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept
        : _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx2>(_Right.get_deleter()), _Right.release()) {}	
	// 拷貝構(gòu)造或者賦值運算符的時候,用于將以前的智能指針置空
	pointer release() noexcept {
        return _STD exchange(_Mypair._Myval2, nullptr);
    }	
	unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

從源碼可以看到,unique_ptr直接delete了拷貝構(gòu)造函數(shù)和operator=賦值重載函數(shù)

禁止用戶對unique_ptr進行顯示的拷貝構(gòu)造和賦值,防止智能指針淺拷貝問題的發(fā)生

但是unique_ptr提供了帶右值引用參數(shù)的拷貝構(gòu)造和賦值

即unique_ptr智能指針可以通過右值引用進行拷貝構(gòu)造和賦值操作

    unique_ptr<int> ptr1(new int);
    unique_ptr<int> ptr2(std::move(ptr1));// 使用右值引用的拷貝構(gòu)造,由于執(zhí)行了release,ptr1已經(jīng)被置空
    cout << (ptr1 == nullptr) << endl;    // true
    ptr2 = std::move(ptr1);               // 使用右值引用的operator=賦值重載函數(shù)
    cout << (ptr2 == nullptr) << endl;    // true

用臨時對象構(gòu)造新的對象時,也會調(diào)用帶右值引用參數(shù)的函數(shù)

unique_ptr<int> get_unique_ptr() {
    unique_ptr<int> tmp(new int);
    return tmp;
}

int main(){
    unique_ptr<int> ptr = get_unique_ptr(); // 調(diào)用帶右值引用參數(shù)的拷貝構(gòu)造函數(shù),由tmp直接構(gòu)造ptr
    return 0;
}

unique_ptr從名字就可以看出來,最終也是只能有一個智能指針引用資源,其他智能指針全部置空

三、帶引用計數(shù)的智能指針

#include <iostream>
#include <memory>
using namespace std;

template<typename T>
class RefCnt {
public:
    RefCnt(T* ptr = nullptr){
        mcount = (mptr == nullptr) ? 0 : 1;
    }

    void addRef() {
        mcount++;
    }
    int subRef() {
        return --mcount;
    }
private:
    int mcount;      // mptr指向某個資源的引用計數(shù),線程不安全。使用atomic_int線程安全
    T* mptr;         //指向智能指針內(nèi)部指向資源的指針,間接指向資源
};
template<typename T>
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr) :_ptr(ptr) {
        cout << "SmartPtr()" << endl;
        mpRefCnt = new RefCnt<int>(_ptr); // 用指向資源的指針初始化引用計數(shù)對象
    }
    ~SmartPtr() {
        if (0 == mpRefCnt->subRef()) {
            cout << "釋放資源析構(gòu)~SmartPtr()" << endl;
            delete _ptr;
        }
        else {
            cout << "空析構(gòu)~SmartPtr()" << endl;
        }
    }
    const T& operator*() const { return *_ptr; }
    T& operator*() { return *_ptr; }
    const T operator->() const { return _ptr; }
    T operator->() { return _ptr; }
    SmartPtr(const SmartPtr<T>& src) :_ptr(src._ptr), mpRefCnt(src.mpRefCnt) {
        cout << "SmartPtr(const SmartPtr<T>& src)" << endl;
        if (_ptr != nullptr) {
            // 用于拷貝的對象已經(jīng)引用了資源
            mpRefCnt->addRef();
        }
    }
    SmartPtr<T>& operator=(const SmartPtr<T>& src) {
        cout << "SmartPtr<T>& operator=" << endl;
        if (this == &src) {
            return *this;
        }
        // 當(dāng)前智能指針指向和src相同的資源,考慮是否釋放之前的資源
        if (0 == mpRefCnt->subRef()) {
            // 若之前指向的資源引用計數(shù)為1,釋放之前的資源
            delete _ptr;
        }
        _ptr = src._ptr;
        mpRefCnt = src.mpRefCnt;
        mpRefCnt->addRef();
        return *this;
    }
private:
    T* _ptr;        // 指向資源的指針
    RefCnt<T>* mpRefCnt; // 指向該資源引用計數(shù)的指針 
};
int main(){
    SmartPtr<int> ptr1(new int);
    SmartPtr<int> ptr2(ptr1);
    SmartPtr<int> ptr3;
    ptr3 = ptr2;
    *ptr2 = 100;
    cout << *ptr2 << " " << *ptr3 << endl;
    return 0;
}

在這里插入圖片描述

四、shared_ptr 和 weak_ptr

  • shared_ptr:強智能指針,可以改變資源的引用計數(shù)
  • weak_ptr:弱智能指針,不可改變資源的引用計數(shù)

weak_ptr -> shared_ptr -> 資源

智能指針的交叉引用問題

#include <iostream>
#include <memory>
using namespace std;
class B;
class A {
public:
	A() {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	shared_ptr<B> _ptrb;
};

class B {
public:
	B() {
		cout << "B()" << endl;
	}
	~B() {
		cout << "~B()" << endl;
	}
	shared_ptr<A> _ptra;
};

int main() {
	shared_ptr<A> pa(new A());
	shared_ptr<B> pb(new B());

	pa->_ptrb = pb;
	pb->_ptra = pa;
	cout << pa.use_count() << endl;
	cout << pb.use_count() << endl;
	return 0;
}

/*
A()
B()
2
2
*/

在這里插入圖片描述

解決辦法: 定義對象時用shared_ptr,引用對象時用weak_ptr

class B;
class A {
public:
	A() {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	weak_ptr<B> _ptrb;
};

class B {
public:
	B() {
		cout << "B()" << endl;
	}
	~B() {
		cout << "~B()" << endl;
	}
	weak_ptr<A> _ptra;
};
/*
A()
B()
1
1
~B()
~A()
*/

weak_ptr只是用于觀察資源,不能夠使用資源,并沒有實現(xiàn)operator*和operator->??梢允褂脀eak_ptr對象的lock()方法返回shared_ptr對象,這個操作會增加資源的引用計數(shù)。

五、多線程訪問共享對象的線程安全問題

線程A和線程B訪問一個共享對象,如果線程A已經(jīng)析構(gòu)這個對象

線程B又要調(diào)用該共享對象的成員方法,線程B再去訪問該對象,就會發(fā)生不可預(yù)期的錯誤

#include <iostream>
#include <memory>
#include <thread>
using namespace std;
class A {
public:
	A() {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	void test() {
		cout << "test()" << endl;
	}
};

void handler01(A* q) {
	// 睡眠2s,使得主線程進行delete
	std::this_thread::sleep_for(std::chrono::seconds(2));
	q->test();
}
int main() {
	A* p = new A();
	thread t1(handler01, p);
	delete p;
	t1.join();
	return 0;
}

在這里插入圖片描述

在多線程訪問共享對象的時候,往往需要lock檢測一下對象是否存在。

開啟一個新線程,并傳入共享對象的弱智能指針。

#include <iostream>
#include <memory>
#include <thread>
using namespace std;
class A {
public:
	A() {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	void test() {
		cout << "test()" << endl;
	}
};

void handler01(weak_ptr<A> pw) {
	shared_ptr<A> ps = pw.lock();
	// lock方法判定資源對象是否析構(gòu)
	if (ps != nullptr) {
		ps->test();
	}else {
		cout << "A對象已經(jīng)析構(gòu),無法訪問" << endl;
	}
}

int main() {
	{
		shared_ptr<A> p(new A());
		//開啟一個新線程,并傳入共享對象的弱智能指針
		thread t1(handler01, weak_ptr<A>(p));
		// 將子線程和主線程的關(guān)聯(lián)分離,也就是說detach()后子線程在后臺獨立繼續(xù)運行,
		// 主線程無法再取得子線程的控制權(quán),即使主線程結(jié)束,子線程未執(zhí)行也不會結(jié)束。
		t1.detach();
	}
	// 讓主線程等待,給時間子線程執(zhí)行,否則main函數(shù)最后會調(diào)用exit方法結(jié)束進程
	std::this_thread::sleep_for(std::chrono::seconds(2));
	
	return 0;
}

在這里插入圖片描述

六、自定義刪除器

通常我們使用智能指針管理的資源是堆內(nèi)存,當(dāng)智能指針出作用域的時候,

在其析構(gòu)函數(shù)中會delete釋放堆內(nèi)存資源,但是除了堆內(nèi)存資源,智能指針還可以管理其它資源,

比如打開的文件,此時對于文件指針的關(guān)閉,就不能用delete了,

這時我們需要自定義智能指針釋放資源的方法。

template<typename T> 
class ArrDeletor {
public:
	// 對象刪除的時候需要調(diào)用對應(yīng)刪除器的()重載函數(shù)
	void operator()(T* ptr) const {
		cout << "ArrDeletor::operator()" << endl;
		delete[] ptr;
	}
};
template<typename T>
class FileDeletor {
public:
	// 對象刪除的時候需要調(diào)用對應(yīng)刪除器的()重載函數(shù)
	void operator()(T* fp) const {
		cout << "FileDeletor::operator()" << endl;
		fclose(fp);
	}
};
int main() {
	unique_ptr<int, ArrDeletor<int>> ptr1(new int[100]);
	unique_ptr<FILE, FileDeletor<FILE>> ptr2(fopen("1.cpp", "w"));
	
	// 使用lambda表達式
	// function<返回值(參數(shù))> 
	// []叫做捕獲說明符,表示一個lambda表達式的開始。接下來是參數(shù)列表,即這個匿名的lambda函數(shù)的參數(shù)
	unique_ptr<int, function<void(int*)>> ptr1(
		new int[100],
		[](int* p)->void {
			cout << "call lambda release new int[]" << endl;
			delete[] p;
		}
	);
	unique_ptr<FILE, function<void(FILE*)>>  ptr2(
		fopen("1.cpp", "w"),
		[](FILE* p)->void {
			cout << "call lambda release fopen(\"1.cpp\", \"w\")" << endl;
			fclose(p);
		}
	);
	return 0;
}

到此這篇關(guān)于C++智能指針教程的文章就介紹到這了,更多相關(guān)C++智能指針內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++實現(xiàn)LeetCode(74.搜索一個二維矩陣)

    C++實現(xiàn)LeetCode(74.搜索一個二維矩陣)

    這篇文章主要介紹了C++實現(xiàn)LeetCode(74.搜索一個二維矩陣),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • VS中scanf為何會報錯詳解

    VS中scanf為何會報錯詳解

    在我們剛使用vs時,在使用scanf函數(shù)時常會遇到報錯提醒,下面這篇文章主要給大家介紹了關(guān)于VS中scanf為何會報錯的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-02-02
  • Qt學(xué)習(xí)之QListWidget控件的使用教程詳解

    Qt學(xué)習(xí)之QListWidget控件的使用教程詳解

    這篇文章主要為大家詳細介紹了Qt中QListWidget控件的使用教程,文中的示例代碼講解詳細,對我們學(xué)習(xí)Qt有一定的幫助,需要的可以參考一下
    2022-12-12
  • C++?RBTree紅黑樹的性質(zhì)與實現(xiàn)

    C++?RBTree紅黑樹的性質(zhì)與實現(xiàn)

    紅黑樹是一種二叉搜索樹,但在每個結(jié)點上增加一個存儲位表示結(jié)點的顏色,可以是Red或Black;通過對任何一條從根到葉子的路徑上各個結(jié)點著色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出倆倍,因而是平衡的
    2023-03-03
  • C++簡明講解類型轉(zhuǎn)換的使用與作用

    C++簡明講解類型轉(zhuǎn)換的使用與作用

    類型轉(zhuǎn)換(type?cast),是高級語言的一個基本語法。它被實現(xiàn)為一個特殊的運算符,以小括號內(nèi)加上類型名來表示,接下來讓我們一起來詳細了解
    2022-04-04
  • 如何理解C++ 臨時變量的常量性

    如何理解C++ 臨時變量的常量性

    這篇文章主要介紹了如何理解C++ 臨時變量的常量性,幫助大家更好的理解和學(xué)習(xí)c++ 變量,感興趣的朋友可以了解下
    2020-08-08
  • C語言中find_package()的搜索路徑的實現(xiàn)

    C語言中find_package()的搜索路徑的實現(xiàn)

    本文主要介紹了C語言中find_package()的搜索路徑的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Qt使用QPainter實現(xiàn)自定義圓形進度條

    Qt使用QPainter實現(xiàn)自定義圓形進度條

    這篇文章主要介紹了Qt如何使用QPainter實現(xiàn)自定義圓形進度條功能,文中的示例代碼講解詳細,對我們學(xué)習(xí)Qt有一定的幫助,需要的可以參考一下
    2022-06-06
  • C++實現(xiàn)掃雷經(jīng)典小游戲

    C++實現(xiàn)掃雷經(jīng)典小游戲

    這篇文章主要為大家詳細介紹了C++實現(xiàn)掃雷經(jīng)典小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • C語言詳解冒泡排序?qū)崿F(xiàn)

    C語言詳解冒泡排序?qū)崿F(xiàn)

    冒泡排序是一種簡單的排序算法,它也是一種穩(wěn)定排序算法。其實現(xiàn)原理是重復(fù)掃描待排序序列,并比較每一對相鄰的元素,當(dāng)該對元素順序不正確時進行交換。一直重復(fù)這個過程,直到?jīng)]有任何兩個相鄰元素可以交換,就表明完成了排序
    2022-04-04

最新評論