精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

告別內存煩惱,C++智能指針來救場

開發 前端
手動內存管理還常引發懸空指針問題,內存釋放后指針未妥善處理,后續對指針的訪問,會觸發未定義行為,致使程序出現詭異錯誤,排查起來困難重重。

在 C++ 編程領域,內存管理向來是棘手難題。手動內存管理依靠new與delete操作符,開發者稍有疏忽,忘記釋放內存,內存泄漏便接踵而至。隨著程序持續運行,泄漏內存不斷累積,不僅拖慢系統速度,甚至能讓程序直接崩潰。項目代碼量一旦增大,內存管理的復雜度呈指數級上升。不同模塊間動態分配內存縱橫交錯,哪塊內存何時該釋放,錯綜復雜,難以厘清。更糟糕的是,異常處理過程中,正常代碼流程被打斷,原本規劃好的內存釋放操作極易被跳過,讓內存泄漏風險倍增。

手動內存管理還常引發懸空指針問題,內存釋放后指針未妥善處理,后續對指針的訪問,會觸發未定義行為,致使程序出現詭異錯誤,排查起來困難重重。好在 C++11 推出智能指針,為開發者帶來曙光。std::unique_ptr、std::shared_ptr和std::weak_ptr各有所長,基于 RAII 原則,將內存分配與對象生命周期緊密關聯,對象生命周期結束,自動釋放內存,極大簡化內存管理流程,讓開發者能專注于核心代碼編寫。

Part1.C++ 編程的內存困境

在 C++ 編程的世界里,內存管理一直是讓人又愛又恨的難題。很多開發者都有過這樣的經歷:辛苦寫好的程序,運行一段時間后,莫名其妙地變得越來越慢,甚至直接崩潰,排查之后才發現是內存泄漏在搞鬼。

比如下面這段簡單的代碼:

void memoryLeakExample() {
    int* ptr = new int(10);
    // 這里忘記delete ptr了!
}

在memoryLeakExample函數中,我們使用new分配了一塊內存來存儲一個整數10 ,但函數結束時卻沒有使用delete釋放這塊內存。隨著這個函數被多次調用,內存泄漏就會越來越嚴重,程序占用的內存會不斷增加,最終導致系統性能下降,甚至崩潰。

除了內存泄漏,空指針引用也是一個常見的噩夢。空指針,就像是指向一個不存在地方的指針,如果不小心對它進行操作,就會引發程序崩潰。比如:

void nullPointerDereferenceExample() {
    int* ptr = nullptr;
    // 嘗試訪問空指針指向的內存,這會導致未定義行為,通常會使程序崩潰
    std::cout << *ptr << std::endl; 
}

在這個例子中,ptr被初始化為nullptr,表示空指針。當我們試圖解引用它(即訪問它指向的內存)時,就會觸發未定義行為,程序很可能會直接崩潰。

這些內存管理問題不僅難以調試,還會嚴重影響程序的穩定性和性能。在大型項目中,代碼邏輯復雜,涉及大量的內存分配和釋放操作,內存問題就更容易隱藏其中,成為一顆隨時可能爆炸的定時炸彈。那么,有沒有什么好辦法來解決這些問題呢?答案就是 C++ 智能指針,它就像是一位貼心的內存管家,能幫我們自動處理很多內存管理的繁瑣事務,讓我們的編程之路更加順暢。

Part2.C++智能指針詳解

2.1智能指針是什么

智能指針是 C++ 中一種特殊的指針類型,它的出現,就像是給普通指針穿上了一層智能鎧甲。與傳統的裸指針不同,智能指針是基于 RAII(Resource Acquisition Is Initialization,資源獲取即初始化)機制實現的。簡單來說,RAII 機制利用對象的生命周期來管理資源,當對象被創建時,資源被獲取;當對象的生命周期結束時,資源會被自動釋放。智能指針正是借助這一機制,能夠自動管理內存的生命周期 ,讓我們無需手動調用delete來釋放內存,從而避免了手動管理內存時可能出現的內存泄漏和內存訪問錯誤等問題。比如:

#include <memory>
#include <iostream>

void smartPtrExample() {
    // 創建一個指向int類型的智能指針,初始值為10
    std::unique_ptr<int> ptr = std::make_unique<int>(10); 
    // 這里無需手動delete ptr,當ptr離開作用域時,內存會自動釋放
    std::cout << *ptr << std::endl; 
}

在上述代碼中,std::unique_ptr<int> ptr = std::make_unique<int>(10);創建了一個std::unique_ptr類型的智能指針ptr,它指向一個動態分配的int型對象,初始值為10。當smartPtrExample函數執行結束,ptr離開作用域時,它所指向的內存會被自動釋放,無需我們手動調用delete。

C++ 中有四種智能指針:

  1. auto_ptr:已經廢棄
  2. unique_ptr:獨占式指針,同一時刻只能有一個指針指向同一個對象
  3. shared_ptr:共享式指針,同一時刻可以有多個指針指向同一個對象
  4. weak_ptr:用來解決shared_ptr相互引用導致的死鎖問題

2.2智能指針存在的意義

智能指針的出現,極大地改變了 C++ 編程中內存管理的方式,它的意義主要體現在以下幾個方面:

  1. 自動內存管理:這是智能指針最顯著的優勢。在傳統的 C++ 編程中,我們需要時刻記住哪些內存是動態分配的,在合適的時候手動釋放,稍有不慎就會導致內存泄漏。而智能指針能夠自動處理內存的釋放,大大降低了內存泄漏的風險,讓我們可以把更多的精力放在業務邏輯的實現上 。
  2. 防止空指針引用:智能指針通過重載operator->和operator*,在訪問指針指向的對象之前,會先檢查指針是否為空。如果指針為空,會拋出異常或者返回一個特定的錯誤值,避免了空指針引用導致的程序崩潰 。
  3. 引用計數機制:以std::shared_ptr為代表的智能指針使用了引用計數機制。多個std::shared_ptr可以共享同一個資源,當有新的std::shared_ptr指向該資源時,引用計數增加;當std::shared_ptr離開作用域或者被重置時,引用計數減少。當引用計數為 0 時,資源會被自動釋放。這種機制非常適合管理那些需要被多個對象共享的資源 。
  4. 類型安全:智能指針是類型相關的,它在編譯時就會進行類型檢查,確保指針操作的安全性,避免了因類型不匹配而導致的難以調試的錯誤 。
  5. 異常安全:在傳統的內存管理中,如果在new和delete之間拋出異常,很容易導致內存泄漏。而智能指針基于 RAII 機制,在對象構造時獲取資源,析構時釋放資源,即使在異常情況下,也能保證資源的正確釋放,提供了更好的異常安全性。

Part3.智能指針的核心原理

3.1 RAII 機制

RAII(Resource Acquisition Is Initialization),即資源獲取即初始化,是智能指針實現自動內存管理的基石。其核心思想是將資源的獲取與對象的初始化緊密綁定,而資源的釋放則與對象的析構函數關聯。當一個對象被創建時,它會獲取所需的資源(例如動態分配的內存),并在對象的生命周期內持有這些資源。一旦對象的生命周期結束,無論是因為函數執行完畢導致局部對象超出作用域,還是因為對象被顯式銷毀,其析構函數都會被自動調用,從而確保資源被正確釋放,避免了因程序員疏忽而導致的資源泄漏問題。

以下是一個簡單的示例代碼,展示了如何通過 RAII 機制實現一個簡單的智能指針:

template<typename T>
class MySmartPtr {
public:
    // 構造函數獲取資源
    MySmartPtr(T* ptr) : m_ptr(ptr) {}

    // 析構函數釋放資源
    ~MySmartPtr() {
        delete m_ptr;
    }

    // 重載解引用運算符,使其行為類似于普通指針
    T& operator*() {
        return *m_ptr;
    }

    // 重載箭頭運算符,使其行為類似于普通指針
    T* operator->() {
        return m_ptr;
    }

private:
    T* m_ptr;
};

在上述代碼中,MySmartPtr類模板實現了一個基本的智能指針功能。構造函數接受一個指針類型的參數,將其賦值給成員變量m_ptr,從而獲取資源。而析構函數則在對象銷毀時,使用delete操作符釋放m_ptr指向的內存資源,確保資源的正確回收。通過這種方式,我們將資源的管理封裝在了類中,利用對象的生命周期來自動管理資源,遵循了 RAII 機制的原則。

3.2引用計數技術

引用計數是智能指針實現資源共享和自動釋放的關鍵技術之一,尤其是在std::shared_ptr中得到了廣泛應用。其原理是為每個被管理的資源維護一個引用計數變量,用于記錄當前有多少個智能指針對象正在引用該資源。

當一個新的std::shared_ptr對象被創建并指向某一資源時,該資源的引用計數會增加。例如:

#include <memory>
#include <iostream>

int main() {
    // 創建一個shared_ptr,此時資源的引用計數為1
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::cout << "ptr1引用計數: " << ptr1.use_count() << std::endl;

    // 拷貝構造一個新的shared_ptr,引用計數增加為2
    std::shared_ptr<int> ptr2 = ptr1;
    std::cout << "ptr2引用計數: " << ptr2.use_count() << std::endl;

    // 賦值操作,引用計數不變(先減少左邊的引用計數,再增加右邊的引用計數)
    std::shared_ptr<int> ptr3;
    ptr3 = ptr2;
    std::cout << "ptr3引用計數: " << ptr3.use_count() << std::endl;

    // 當一個shared_ptr超出作用域,引用計數減少
    {
        std::shared_ptr<int> ptr4 = ptr3;
        std::cout << "ptr4引用計數: " << ptr4.use_count() << std::endl;
    }
    std::cout << "ptr3引用計數(ptr4超出作用域后): " << ptr3.use_count() << std::endl;

    return 0;
}

在上述代碼中,通過std::make_shared創建了一個std::shared_ptr<int>對象ptr1,此時資源的引用計數為 1。接著通過拷貝構造和賦值操作創建了ptr2和ptr3,每次操作都會使引用計數相應增加。當ptr4超出其作用域時,其析構函數被調用,引用計數減少。

當引用計數變為 0 時,表示沒有任何智能指針再引用該資源,此時資源會被自動釋放。這種機制確保了資源在不再被使用時能夠及時、正確地被回收,避免了內存泄漏的發生,同時也支持了多個智能指針安全地共享同一資源,提高了資源的利用率和程序的靈活性。

Part4.常見智能指針類型詳解

4.1 unique_ptr:獨占資源的小衛士

std::unique_ptr是 C++11 引入的一種智能指針,它采用獨占所有權語義,就像是一位獨占資源的小衛士,在同一時間內,只能有一個std::unique_ptr指向給定的資源 。當std::unique_ptr離開作用域時,它所管理的資源會被自動釋放,從而避免了內存泄漏。

圖片圖片

我們來看一下它的定義和初始化方式:

#include <memory>
#include <iostream>

int main() {
    // 直接初始化,使用new創建一個int型對象,值為10
    std::unique_ptr<int> ptr1(new int(10)); 
    // 使用C++14引入的make_unique函數來初始化,更加簡潔安全
    std::unique_ptr<int> ptr2 = std::make_unique<int>(20); 

    // 訪問智能指針指向的值
    std::cout << "ptr1的值: " << *ptr1 << std::endl; 
    std::cout << "ptr2的值: " << *ptr2 << std::endl; 

    return 0;
}

在上述代碼中,ptr1通過直接初始化的方式,指向一個動態分配的int型對象,值為10;ptr2則使用std::make_unique函數進行初始化,指向一個值為20的int型對象 。當main函數結束時,ptr1和ptr2離開作用域,它們所指向的內存會被自動釋放。

std::unique_ptr不支持拷貝,但支持移動語義。這意味著我們可以將一個std::unique_ptr的所有權轉移給另一個std::unique_ptr 。比如:

#include <memory>
#include <iostream>

int main() {
    std::unique_ptr<int> ptr1 = std::make_unique<int>(10); 
    // 使用std::move將ptr1的所有權轉移給ptr2
    std::unique_ptr<int> ptr2 = std::move(ptr1); 

    // 此時ptr1不再指向任何對象,為空指針
    if (!ptr1) { 
        std::cout << "ptr1為空指針" << std::endl; 
    }
    // ptr2指向原來ptr1指向的對象
    if (ptr2) { 
        std::cout << "ptr2的值: " << *ptr2 << std::endl; 
    }

    return 0;
}

在這段代碼中,std::move(ptr1)將ptr1的所有權轉移給了ptr2 ,轉移后ptr1不再指向任何對象,變為空指針,而ptr2則指向了原來ptr1指向的對象。

4.2 shared_ptr:資源共享的協調者

std::shared_ptr是一種可共享所有權的智能指針,它就像是資源共享的協調者,允許多個std::shared_ptr指向同一個資源。std::shared_ptr通過引用計數機制來管理資源,每個std::shared_ptr對象都維護著一個引用計數器,用于記錄指向同一資源的std::shared_ptr對象的數量。當有新的std::shared_ptr指向該資源時,引用計數增加;當std::shared_ptr離開作用域或者被重置時,引用計數減少。當引用計數為 0 時,資源會被自動釋放。

圖片圖片

下面是std::shared_ptr的定義和初始化方式示例:

#include <memory>
#include <iostream>

int main() {
    // 直接初始化,使用new創建一個int型對象,值為10
    std::shared_ptr<int> ptr1(new int(10)); 
    // 使用make_shared函數初始化,推薦這種方式,效率更高
    std::shared_ptr<int> ptr2 = std::make_shared<int>(20); 

    // 輸出引用計數
    std::cout << "ptr1的引用計數: " << ptr1.use_count() << std::endl; 
    std::cout << "ptr2的引用計數: " << ptr2.use_count() << std::endl; 

    // 讓ptr3也指向ptr1所指向的對象,引用計數增加
    std::shared_ptr<int> ptr3 = ptr1; 
    std::cout << "ptr1的引用計數: " << ptr1.use_count() << std::endl; 

    return 0;
}

在上述代碼中,ptr1通過直接初始化指向一個值為10的int型對象,ptr2使用make_shared函數初始化指向一個值為20的int型對象 。一開始,ptr1和ptr2的引用計數都為1 。當ptr3 = ptr1時,ptr1所指向對象的引用計數增加為2 ,因為現在有ptr1和ptr3兩個std::shared_ptr指向同一個對象。

再看一個更復雜的例子,展示std::shared_ptr在對象生命周期管理中的作用:

#include <memory>
#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass構造函數被調用" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass析構函數被調用" << std::endl;
    }
};

int main() {
    {
        std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(); 
        std::shared_ptr<MyClass> ptr2 = ptr1; 

        // 輸出引用計數
        std::cout << "ptr1的引用計數: " << ptr1.use_count() << std::endl; 
        std::cout << "ptr2的引用計數: " << ptr2.use_count() << std::endl; 
    }
    // 這里ptr1和ptr2離開作用域,引用計數降為0,MyClass對象被銷毀
    std::cout << "離開作用域后" << std::endl; 

    return 0;
}

在這個例子中,MyClass類有構造函數和析構函數,用于輸出對象的創建和銷毀信息。在main函數中,創建了ptr1和ptr2兩個std::shared_ptr,它們都指向同一個MyClass對象 。當程序執行到}時,ptr1和ptr2離開作用域,它們對MyClass對象的引用計數降為0 ,MyClass對象的析構函數被調用,輸出MyClass析構函數被調用。

4.3 weak_ptr:解決循環引用的利器

std::weak_ptr是一種可觀察std::shared_ptr所管理對象的智能指針,但它不會增加對象的引用計數,就像是一個默默觀察的旁觀者。std::weak_ptr主要用于解決std::shared_ptr可能出現的循環引用問題。

圖片圖片

循環引用是指兩個或多個對象通過std::shared_ptr相互引用,導致它們的引用計數永遠無法降為 0,從而造成內存泄漏。比如下面這個錯誤示例:

#include <memory>
#include <iostream>

class B;

class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() {
        std::cout << "A的析構函數被調用" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> a_ptr;
    ~B() {
        std::cout << "B的析構函數被調用" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->b_ptr = b;
    b->a_ptr = a;

    // 這里a和b離開作用域,但由于循環引用,它們的引用計數不會降為0,A和B對象不會被銷毀
    return 0;
}

在上述代碼中,A類和B類通過std::shared_ptr相互引用,形成了循環引用。當main函數結束時,a和b離開作用域,但由于循環引用,它們的引用計數不會降為 0,A和B對象不會被銷毀,從而導致內存泄漏。

為了解決這個問題,我們可以使用std::weak_ptr ,修改后的代碼如下:

#include <memory>
#include <iostream>

class B;

class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() {
        std::cout << "A的析構函數被調用" << std::endl;
    }
};

class B {
public:
    std::weak_ptr<A> a_ptr;
    ~B() {
        std::cout << "B的析構函數被調用" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->b_ptr = b;
    b->a_ptr = a;

    // 這里a和b離開作用域,由于b中的a_ptr是weak_ptr,不會增加引用計數,A和B對象會被正確銷毀
    return 0;
}

在修改后的代碼中,B類中的a_ptr改為了std::weak_ptr ,這樣就打破了循環引用。當main函數結束時,a和b離開作用域,A和B對象的引用計數能夠正確降為 0,它們的析構函數被調用,對象被正確銷毀。

std::weak_ptr本身不能直接訪問所指向的對象,需要通過lock方法將其轉換為std::shared_ptr ,然后再訪問對象。比如:

#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(10);
    std::weak_ptr<int> weakPtr = sharedPtr;

    // 使用lock方法將weak_ptr轉換為shared_ptr
    if (std::shared_ptr<int> tempPtr = weakPtr.lock()) { 
        std::cout << "通過weak_ptr訪問的值: " << *tempPtr << std::endl; 
    } else {
        std::cout << "對象已被銷毀" << std::endl;
    }

    return 0;
}

在這段代碼中,首先創建了一個std::shared_ptr<int>對象sharedPtr,然后創建了一個std::weak_ptr<int>對象weakPtr,并讓它指向sharedPtr所指向的對象 。通過weakPtr.lock()方法將weakPtr轉換為std::shared_ptr<int> ,如果轉換成功(即對象未被銷毀),則可以通過返回的std::shared_ptr<int>訪問對象的值;如果對象已被銷毀,lock方法會返回一個空的std::shared_ptr 。

Part5.智能指針的使用技巧

5.1選擇合適的智能指針類型

在實際編程中,選擇合適的智能指針類型至關重要,它直接關系到程序的性能、資源管理的有效性以及代碼的穩定性。

當我們需要獨占某個對象的所有權,確保在對象的生命周期內只有一個指針能夠訪問和管理它時,std::unique_ptr是不二之選。例如,在一個函數內部創建的對象,只在該函數內部使用,并且不需要將其所有權傳遞給其他部分的代碼,就可以使用std::unique_ptr。像下面這樣的代碼場景:

#include <iostream>
#include <memory>

void processResource() {
    // 使用std::unique_ptr獨占管理一個Resource對象
    std::unique_ptr<int> ptr = std::make_unique<int>(42);
    std::cout << *ptr << std::endl;
    // 函數結束時,ptr自動析構,所管理的int對象也被釋放
}

int main() {
    processResource();
    return 0;
}

在上述代碼中,processResource函數內部創建的int對象通過std::unique_ptr進行管理,當函數執行完畢,ptr超出作用域,其析構函數會自動釋放所指向的int對象,保證了資源的正確回收,同時避免了其他部分代碼對該對象的意外訪問和修改。

而當多個對象需要共享同一塊內存資源時,std::shared_ptr就派上用場了。比如在一個多線程環境下,多個線程可能同時訪問和操作同一個對象,此時使用std::shared_ptr可以方便地實現資源的共享,并且保證對象在所有引用它的指針都銷毀后才被釋放。例如:

#include <iostream>
#include <memory>
#include <thread>
#include <vector>

class SharedResource {
public:
    SharedResource() {
        std::cout << "SharedResource constructed." << std::endl;
    }

    ~SharedResource() {
        std::cout << "SharedResource destroyed." << std::endl;
    }

    void doSomething() {
        std::cout << "Doing something with the shared resource." << std::endl;
    }
};

void threadFunction(std::shared_ptr<SharedResource> ptr) {
    ptr->doSomething();
}

int main() {
    // 創建一個指向SharedResource對象的shared_ptr
    std::shared_ptr<SharedResource> sharedPtr = std::make_shared<SharedResource>();
    std::vector<std::thread> threads;

    // 創建多個線程,每個線程都傳入共享的shared_ptr
    for (int i = 0; i < 5; ++i) {
        threads.push_back(std::thread(threadFunction, sharedPtr));
    }

    // 等待所有線程完成
    for (auto& th : threads) {
        th.join();
    }

    return 0;
}

在上述代碼中,SharedResource對象通過std::shared_ptr進行管理,在多個線程中都可以安全地訪問和操作這個共享對象。每個線程函數threadFunction都接受一個std::shared_ptr作為參數,這樣多個線程就可以共享同一個SharedResource對象,而對象的生命周期由std::shared_ptr的引用計數機制來自動管理,當所有線程都結束,不再有std::shared_ptr指向該對象時,對象會被自動銷毀。

然而,正如前面所提到的,std::shared_ptr在使用過程中可能會出現循環引用的問題。為了避免這種情況,當我們遇到對象之間存在相互引用,但又不希望因為這種引用關系導致內存泄漏時,就需要引入std::weak_ptr。例如在一個樹形數據結構中,節點之間可能存在父子節點的相互引用,如果使用std::shared_ptr來管理節點,就很容易出現循環引用,導致節點無法正常釋放。此時,我們可以將父節點對子節點的引用使用std::shared_ptr,而子節點對父節點的引用使用std::weak_ptr,這樣就可以打破循環引用,保證對象能夠在合適的時候被正確銷毀。

5.2選擇合適的智能指針類型

在實際編程中,選擇合適的智能指針類型至關重要,它直接關系到程序的性能、資源管理的有效性以及代碼的穩定性。

當我們需要獨占某個對象的所有權,確保在對象的生命周期內只有一個指針能夠訪問和管理它時,std::unique_ptr是不二之選。例如,在一個函數內部創建的對象,只在該函數內部使用,并且不需要將其所有權傳遞給其他部分的代碼,就可以使用std::unique_ptr。像下面這樣的代碼場景:

#include <iostream>
#include <memory>

void processResource() {
    // 使用std::unique_ptr獨占管理一個Resource對象
    std::unique_ptr<int> ptr = std::make_unique<int>(42);
    std::cout << *ptr << std::endl;
    // 函數結束時,ptr自動析構,所管理的int對象也被釋放
}

int main() {
    processResource();
    return 0;
}

在上述代碼中,processResource函數內部創建的int對象通過std::unique_ptr進行管理,當函數執行完畢,ptr超出作用域,其析構函數會自動釋放所指向的int對象,保證了資源的正確回收,同時避免了其他部分代碼對該對象的意外訪問和修改。

而當多個對象需要共享同一塊內存資源時,std::shared_ptr就派上用場了。比如在一個多線程環境下,多個線程可能同時訪問和操作同一個對象,此時使用std::shared_ptr可以方便地實現資源的共享,并且保證對象在所有引用它的指針都銷毀后才被釋放。例如:

#include <iostream>
#include <memory>
#include <thread>
#include <vector>

class SharedResource {
public:
    SharedResource() {
        std::cout << "SharedResource constructed." << std::endl;
    }

    ~SharedResource() {
        std::cout << "SharedResource destroyed." << std::endl;
    }

    void doSomething() {
        std::cout << "Doing something with the shared resource." << std::endl;
    }
};

void threadFunction(std::shared_ptr<SharedResource> ptr) {
    ptr->doSomething();
}

int main() {
    // 創建一個指向SharedResource對象的shared_ptr
    std::shared_ptr<SharedResource> sharedPtr = std::make_shared<SharedResource>();
    std::vector<std::thread> threads;

    // 創建多個線程,每個線程都傳入共享的shared_ptr
    for (int i = 0; i < 5; ++i) {
        threads.push_back(std::thread(threadFunction, sharedPtr));
    }

    // 等待所有線程完成
    for (auto& th : threads) {
        th.join();
    }

    return 0;
}

在上述代碼中,SharedResource對象通過std::shared_ptr進行管理,在多個線程中都可以安全地訪問和操作這個共享對象。每個線程函數threadFunction都接受一個std::shared_ptr作為參數,這樣多個線程就可以共享同一個SharedResource對象,而對象的生命周期由std::shared_ptr的引用計數機制來自動管理,當所有線程都結束,不再有std::shared_ptr指向該對象時,對象會被自動銷毀。

然而,正如前面所提到的,std::shared_ptr在使用過程中可能會出現循環引用的問題。為了避免這種情況,當我們遇到對象之間存在相互引用,但又不希望因為這種引用關系導致內存泄漏時,就需要引入std::weak_ptr。例如在一個樹形數據結構中,節點之間可能存在父子節點的相互引用,如果使用std::shared_ptr來管理節點,就很容易出現循環引用,導致節點無法正常釋放。此時,我們可以將父節點對子節點的引用使用std::shared_ptr,而子節點對父節點的引用使用std::weak_ptr,這樣就可以打破循環引用,保證對象能夠在合適的時候被正確銷毀。

5.3與容器的結合使用

智能指針與 C++ 標準容器的結合使用,為我們在管理對象集合時提供了極大的便利,同時也能有效地避免內存泄漏和懸空指針等問題。

在容器中存儲智能指針時,我們可以像存儲普通對象一樣將智能指針放入容器中。例如,使用std::vector來存儲std::unique_ptr指向的對象:

#include <iostream>
#include <memory>
#include <vector>

class MyClass {
public:
    MyClass(int num) : num_(num) {
        std::cout << "MyClass " << num_ << " constructed." << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass " << num_ << " destroyed." << std::endl;
    }

    void print() const {
        std::cout << "MyClass " << num_ << std::endl;
    }

private:
    int num_;
};

int main() {
    std::vector<std::unique_ptr<MyClass>> vec;
    // 創建多個MyClass對象,并通過unique_ptr管理,放入向量中
    for (int i = 0; i < 5; ++i) {
        vec.push_back(std::make_unique<MyClass>(i));
    }

    // 遍歷向量,調用每個對象的print函數
    for (const auto& ptr : vec) {
        ptr->print();
    }

    return 0;
}

在上述代碼中,std::vector存儲了std::unique_ptr<MyClass>類型的元素,每個std::unique_ptr都獨占管理一個MyClass對象。通過這種方式,我們可以方便地管理一組對象,并且不用擔心對象的生命周期問題,因為當std::unique_ptr超出作用域時(例如從容器中移除或者容器本身被銷毀),它所管理的對象會自動被析構,從而避免了內存泄漏。

當使用std::shared_ptr與容器結合時,同樣可以實現對象的共享管理。例如,在一個std::list中存儲std::shared_ptr指向的對象:

#include <iostream>
#include <memory>
#include <list>

class SharedResource {
public:
    SharedResource() {
        std::cout << "SharedResource constructed." << std::endl;
    }

    ~SharedResource() {
        std::cout << "SharedResource destroyed." << std::endl;
    }

    void doSomething() {
        std::cout << "Doing something with the shared resource." << std::endl;
    }
};

int main() {
    std::list<std::shared_ptr<SharedResource>> myList;
    // 創建一個SharedResource對象,并通過shared_ptr管理,放入列表中
    std::shared_ptr<SharedResource> sharedPtr = std::make_shared<SharedResource>();
    myList.push_back(sharedPtr);

    // 從列表中取出shared_ptr,并調用對象的方法
    for (const auto& ptr : myList) {
        ptr->doSomething();
    }

    return 0;
}

在這個例子中,std::list中的多個元素可以共享同一個SharedResource對象,通過std::shared_ptr的引用計數機制來確保對象在所有引用它的指針都被銷毀后才被釋放,保證了資源的正確管理。

需要注意的是,在使用容器存儲智能指針時,要避免一些可能導致問題的操作。例如,不要在容器中存儲已經被析構的智能指針,否則可能會導致未定義行為。同時,當對容器進行插入、刪除或者修改操作時,要確保智能指針的生命周期仍然在有效的控制范圍內,以防止出現懸空指針或者內存泄漏的情況。

Part6.智能指針的性能分析

6.1內存開銷

在分析智能指針的內存開銷時,我們需要考慮多個因素,包括引用計數的存儲、控制塊的大小等。

std::shared_ptr的內存占用相對較大。它除了要存儲指向對象的指針外,還需要維護一個引用計數,以及一個包含引用計數、弱引用計數、刪除器、分配器等信息的控制塊。在常見的編譯器和運行環境下,一個std::shared_ptr對象的大小通常是裸指針大小的兩倍。例如,在 64 位系統中,裸指針的大小為 8 字節,而std::shared_ptr的大小可能達到 16 字節左右。這是因為它需要額外的空間來存儲引用計數和控制塊信息,以實現資源的共享和生命周期的管理。

以下是一個簡單的代碼示例,用于展示std::shared_ptr的內存占用情況:

#include <iostream>
#include <memory>

class MyClass {
public:
    int data;
};

int main() {
    std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
    std::cout << "Size of std::shared_ptr: " << sizeof(ptr) << " bytes" << std::endl;
    std::cout << "Size of raw pointer: " << sizeof(MyClass*) << " bytes" << std::endl;
    return 0;
}

在上述代碼中,通過sizeof運算符可以大致了解std::shared_ptr和裸指針的內存占用情況。

相比之下,std::unique_ptr的內存開銷則較小。它只需要存儲指向對象的指針,不需要額外的引用計數和控制塊,因此其大小與裸指針基本相同。在 64 位系統中,std::unique_ptr的大小通常也為 8 字節,與指向相同類型對象的裸指針大小一致。例如:

#include <iostream>
#include <memory>

class MyClass {
public:
    int data;
};

int main() {
    std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
    std::cout << "Size of std::unique_ptr: " << sizeof(ptr) << " bytes" << std::endl;
    std::cout << "Size of raw pointer: " << sizeof(MyClass*) << " bytes" << std::endl;
    return 0;
}

在對內存敏感的場景中,如嵌入式系統開發或者對內存使用要求極為嚴格的高性能計算場景,如果不需要資源的共享,應優先考慮使用std::unique_ptr,以減少不必要的內存開銷。

6.2運行時效率

在運行時效率方面,智能指針的不同操作會帶來不同程度的開銷。

std::shared_ptr的拷貝和賦值操作相對較為復雜,因為它們需要更新引用計數,這涉及到原子操作(在多線程環境下)或者簡單的計數增減(在單線程環境下),會帶來一定的性能開銷。例如,在一個頻繁進行對象拷貝和賦值的場景中,如果使用std::shared_ptr,可能會導致程序的執行速度變慢。

#include <iostream>
#include <memory>
#include <vector>

class MyClass {
public:
    MyClass() {}
    ~MyClass() {}
};

int main() {
    std::vector<std::shared_ptr<MyClass>> vec;
    for (int i = 0; i < 1000000; ++i) {
        // 頻繁創建和拷貝shared_ptr
        std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
        vec.push_back(ptr);
    }
    return 0;
}

在上述代碼中,創建了大量的std::shared_ptr并進行拷貝操作,會消耗一定的時間和資源來維護引用計數。

std::unique_ptr的移動操作則相對高效,因為它只是簡單地轉移了對象的所有權,不需要進行復雜的計數操作,類似于將一個指針賦值給另一個指針,開銷較小。例如:

#include <iostream>
#include <memory>
#include <vector>

class MyClass {
public:
    MyClass() {}
    ~MyClass() {}
};

int main() {
    std::vector<std::unique_ptr<MyClass>> vec;
    for (int i = 0; i < 1000000; ++i) {
        // 頻繁創建和移動unique_ptr
        std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
        vec.push_back(std::move(ptr));
    }
    return 0;
}

在多線程環境下,std::shared_ptr的引用計數操作是原子性的,這保證了在多個線程同時對同一個std::shared_ptr進行拷貝、賦值或者析構等操作時,引用計數的正確性,避免了數據競爭和內存泄漏等問題。但原子操作本身會帶來一定的性能開銷,相比之下,std::unique_ptr在多線程環境下,如果不需要共享資源,其獨占所有權的特性使得它在并發場景中更加高效,不需要額外的同步機制來保證引用計數的正確性。

為了優化智能指針的性能,可以考慮以下幾點:

  • 在不需要共享資源的情況下,盡量使用std::unique_ptr,避免std::shared_ptr的引用計數開銷。
  • 對于std::shared_ptr,盡量減少不必要的拷貝和賦值操作,可以通過合理的對象設計和編程邏輯,減少對象的生命周期交叉,從而降低引用計數的更新頻率。
  • 在多線程環境下,如果使用std::shared_ptr,要注意避免頻繁的線程切換和競爭,盡量將共享資源的訪問和操作集中在一個線程或者通過合適的同步機制進行協調,以減少原子操作的開銷。

通過實際的性能測試數據可以更直觀地了解智能指針的性能差異。例如,使用專業的性能測試工具,對不同智能指針在相同操作場景下的執行時間、內存使用情況等指標進行測量,可以發現std::unique_ptr在簡單的對象生命周期管理場景中,執行速度通常比std::shared_ptr快,尤其是在對象頻繁創建和銷毀的情況下。而std::shared_ptr在需要資源共享的場景中,雖然存在一定的性能開銷,但它提供的共享機制是std::unique_ptr無法替代的,在實際應用中需要根據具體的需求來權衡選擇合適的智能指針類型,并結合適當的優化策略,以達到最佳的性能表現。

Part7.實際場景應用案例

案例一:資源管理

在實際開發中,很多時候我們需要管理一些系統資源,比如文件句柄、數據庫連接等。如果這些資源沒有被正確釋放,會導致資源浪費,甚至影響整個系統的穩定性。智能指針在這方面能發揮很大的作用。

假設我們開發一個文件處理程序,需要讀取文件內容并進行一些處理。在傳統的方式中,我們需要手動打開文件、讀取內容,最后關閉文件。如果在讀取過程中出現異常,很容易忘記關閉文件,導致文件句柄泄漏。使用std::unique_ptr結合自定義刪除器,可以很好地解決這個問題。代碼示例如下:

#include <iostream>
#include <memory>
#include <fstream>

// 自定義文件關閉函數
void closeFile(std::ifstream* file) {
    if (file->is_open()) {
        file->close();
    }
    delete file;
}

void processFile(const std::string& filename) {
    // 使用std::unique_ptr管理文件句柄,傳入自定義刪除器closeFile
    std::unique_ptr<std::ifstream, decltype(&closeFile)> file(new std::ifstream(filename), closeFile); 
    if (!file) {
        std::cerr << "無法打開文件: " << filename << std::endl;
        return;
    }

    std::string line;
    while (std::getline(*file, line)) {
        // 處理文件內容,這里簡單打印每一行
        std::cout << line << std::endl; 
    }
}

int main() {
    processFile("example.txt");
    return 0;
}

在上述代碼中,std::unique_ptr<std::ifstream, decltype(&closeFile)> file(new std::ifstream(filename), closeFile);創建了一個std::unique_ptr對象file,用于管理std::ifstream類型的文件句柄。第二個參數decltype(&closeFile)指定了自定義刪除器closeFile,當file離開作用域時,會自動調用closeFile函數來關閉文件并釋放內存,確保文件句柄被正確釋放,避免了資源泄漏。

再來看一個數據庫連接的例子。在一個簡單的數據庫操作程序中,使用std::unique_ptr管理數據庫連接對象,確保連接在不再需要時被正確關閉。假設我們使用 MySQL C++ Connector 庫,示例代碼如下:

#include <memory>
#include <mysql/mysqlx.hpp>

// 自定義數據庫連接關閉函數
void closeConnection(mysqlx::Session* session) {
    session->close();
    delete session;
}

void performDatabaseOperations() {
    // 建立數據庫連接,這里的連接參數是示例,實際中需要根據數據庫配置修改
    std::unique_ptr<mysqlx::Session, decltype(&closeConnection)> session(new mysqlx::Session("localhost", 33060, "user", "password"), closeConnection); 

    // 使用session進行數據庫操作,這里簡單查詢一個表
    auto schema = session->getSchema("test_schema");
    auto table = schema.getTable("test_table");
    auto result = table.select("*").execute();
    while (auto row = result.fetchOne()) {
        // 處理查詢結果,這里簡單打印每一行
        std::cout << row[0] << " " << row[1] << std::endl; 
    }
}

int main() {
    performDatabaseOperations();
    return 0;
}

在這個例子中,std::unique_ptr<mysqlx::Session, decltype(&closeConnection)> session(new mysqlx::Session("localhost", 33060, "user", "password"), closeConnection);創建了一個std::unique_ptr對象session來管理數據庫連接。當session離開作用域時,自定義刪除器closeConnection會被調用,關閉數據庫連接并釋放內存,有效避免了數據庫連接泄漏。

案例二:對象生命周期管理

在游戲開發中,管理游戲角色對象的生命周期是一個常見且重要的任務。每個游戲角色都有自己的屬性和行為,并且在游戲運行過程中,角色可能會被創建、銷毀或者切換狀態。如果使用傳統的裸指針來管理這些角色對象,很容易出現內存泄漏和懸空指針的問題,影響游戲的性能和穩定性。而智能指針可以幫助我們輕松地管理游戲角色對象的生命周期,讓開發者能夠更加專注于游戲邏輯的實現。

以一個簡單的角色扮演游戲(RPG)為例,我們有一個Character類來表示游戲角色,每個角色有名字、生命值、攻擊力等屬性,以及移動、攻擊等行為。使用std::shared_ptr來管理Character對象,這樣多個游戲系統(如戰斗系統、場景系統等)可以共享同一個角色對象,而不用擔心對象的生命周期問題。示例代碼如下:

#include <iostream>
#include <memory>
#include <string>

class Character {
public:
    Character(const std::string& name, int health, int attack)
        : name(name), health(health), attack(attack) {}

    void move(int x, int y) {
        std::cout << name << " 移動到坐標 (" << x << ", " << y << ")" << std::endl;
    }

    void attack(Character& target) {
        target.health -= attack;
        std::cout << name << " 攻擊了 " << target.name << "," << target.name << " 的生命值剩余: " << target.health << std::endl;
    }

private:
    std::string name;
    int health;
    int attack;
};

void battle(std::shared_ptr<Character> attacker, std::shared_ptr<Character> target) {
    attacker->attack(*target);
}

int main() {
    // 創建兩個游戲角色
    std::shared_ptr<Character> player1 = std::make_shared<Character>("戰士", 100, 20); 
    std::shared_ptr<Character> player2 = std::make_shared<Character>("法師", 80, 15); 

    // 進行戰斗
    battle(player1, player2); 

    return 0;
}

在上述代碼中,std::shared_ptr<Character> player1 = std::make_shared<Character>("戰士", 100, 20);和std::shared_ptr<Character> player2 = std::make_shared<Character>("法師", 80, 15);分別創建了兩個std::shared_ptr對象player1和player2,指向兩個Character對象。在battle函數中,attacker->attack(*target);通過std::shared_ptr來調用角色的攻擊方法,實現了戰斗邏輯。當player1和player2離開作用域時,由于std::shared_ptr的引用計數機制,只有當沒有任何std::shared_ptr指向對應的Character對象時,對象才會被銷毀,從而確保了游戲角色對象的生命周期被正確管理。

再考慮一種更復雜的情況,游戲中存在一個場景,場景中包含多個游戲角色,并且角色可能會進入或離開場景。我們可以使用std::vector<std::shared_ptr<Character>>來管理場景中的角色。示例代碼如下:

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Character {
public:
    Character(const std::string& name, int health, int attack)
        : name(name), health(health), attack(attack) {}

    void move(int x, int y) {
        std::cout << name << " 移動到坐標 (" << x << ", " << y << ")" << std::endl;
    }

    void attack(Character& target) {
        target.health -= attack;
        std::cout << name << " 攻擊了 " << target.name << "," << target.name << " 的生命值剩余: " << target.health << std::endl;
    }

private:
    std::string name;
    int health;
    int attack;
};

class Scene {
public:
    void addCharacter(const std::shared_ptr<Character>& character) {
        characters.push_back(character);
        std::cout << character->name << " 進入了場景" << std::endl;
    }

    void removeCharacter(const std::shared_ptr<Character>& character) {
        for (auto it = characters.begin(); it != characters.end(); ++it) {
            if (*it == character) {
                characters.erase(it);
                std::cout << character->name << " 離開了場景" << std::endl;
                return;
            }
        }
    }

    void displayCharacters() {
        std::cout << "場景中的角色有: ";
        for (const auto& character : characters) {
            std::cout << character->name << " ";
        }
        std::cout << std::endl;
    }

private:
    std::vector<std::shared_ptr<Character>> characters;
};

int main() {
    std::shared_ptr<Character> player1 = std::make_shared<Character>("戰士", 100, 20);
    std::shared_ptr<Character> player2 = std::make_shared<Character>("法師", 80, 15);

    Scene scene;
    scene.addCharacter(player1);
    scene.addCharacter(player2);
    scene.displayCharacters();

    scene.removeCharacter(player1);
    scene.displayCharacters();

    return 0;
}

在這個例子中,Scene類使用std::vector<std::shared_ptr<Character>>來存儲場景中的角色。addCharacter方法用于將角色添加到場景中,removeCharacter方法用于將角色從場景中移除,displayCharacters方法用于顯示場景中的所有角色。通過std::shared_ptr,我們可以方便地管理角色在場景中的生命周期,并且可以在不同的場景和游戲系統中共享角色對象,大大簡化了游戲開發中對象生命周期管理的復雜性 。

Part8.智能指針避坑指南

8.1循環引用問題

在使用std::shared_ptr時,循環引用是一個需要特別注意的問題。當兩個或多個對象通過std::shared_ptr相互引用時,就會形成循環引用。這種情況下,對象的引用計數永遠不會降為 0,導致內存無法釋放,從而造成內存泄漏。

我們來看一個具體的示例:

#include <memory>
#include <iostream>

class B;

class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() {
        std::cout << "A的析構函數被調用" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> a_ptr;
    ~B() {
        std::cout << "B的析構函數被調用" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->b_ptr = b;
    b->a_ptr = a;

    // 這里a和b離開作用域,但由于循環引用,它們的引用計數不會降為0,A和B對象不會被銷毀
    return 0;
}

在上述代碼中,A類和B類通過std::shared_ptr相互引用,形成了循環引用。當main函數結束時,a和b離開作用域,但由于循環引用,它們的引用計數不會降為 0,A和B對象不會被銷毀,從而導致內存泄漏。

為了解決循環引用問題,我們可以使用std::weak_ptr 。std::weak_ptr是一種弱引用指針,它不會增加對象的引用計數。當std::weak_ptr指向的對象被銷毀時,std::weak_ptr會自動失效。我們將上述代碼修改如下:

#include <memory>
#include <iostream>

class B;

class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() {
        std::cout << "A的析構函數被調用" << std::endl;
    }
};

class B {
public:
    std::weak_ptr<A> a_ptr;
    ~B() {
        std::cout << "B的析構函數被調用" << std::endl;
    }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->b_ptr = b;
    b->a_ptr = a;

    // 這里a和b離開作用域,由于b中的a_ptr是weak_ptr,不會增加引用計數,A和B對象會被正確銷毀
    return 0;
}

在修改后的代碼中,B類中的a_ptr改為了std::weak_ptr ,這樣就打破了循環引用。當main函數結束時,a和b離開作用域,A和B對象的引用計數能夠正確降為 0,它們的析構函數被調用,對象被正確銷毀。

8.2性能考慮

在使用智能指針時,性能也是一個需要考慮的因素。不同類型的智能指針在性能上有一定的差異,尤其是std::shared_ptr,由于其引用計數機制,會帶來一些額外的開銷。

std::shared_ptr使用引用計數來管理對象的生命周期,每次復制或銷毀std::shared_ptr時,都需要更新引用計數。這個過程需要進行原子操作,以確保在多線程環境下的正確性,這就會帶來一定的性能開銷。例如,在一個性能敏感的循環中,如果頻繁地創建、復制和銷毀std::shared_ptr,可能會對程序的性能產生較大的影響。

我們來看一個簡單的性能測試示例,比較使用std::unique_ptr和std::shared_ptr在大量對象創建和銷毀時的性能差異:

#include <iostream>
#include <memory>
#include <chrono>

const int numObjects = 1000000;

void testUniquePtrPerformance() {
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < numObjects; ++i) {
        std::unique_ptr<int> ptr = std::make_unique<int>(i);
        // 這里可以進行一些對ptr的操作,為了簡單,此處省略
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "使用std::unique_ptr的時間: " << duration << " 毫秒" << std::endl;
}

void testSharedPtrPerformance() {
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < numObjects; ++i) {
        std::shared_ptr<int> ptr = std::make_shared<int>(i);
        // 這里可以進行一些對ptr的操作,為了簡單,此處省略
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "使用std::shared_ptr的時間: " << duration << " 毫秒" << std::endl;
}

int main() {
    testUniquePtrPerformance();
    testSharedPtrPerformance();
    return 0;
}

在這個示例中,testUniquePtrPerformance函數使用std::unique_ptr進行了numObjects次對象的創建和銷毀操作,testSharedPtrPerformance函數則使用std::shared_ptr進行相同的操作。通過測量這兩個函數的執行時間,可以直觀地看到std::shared_ptr由于引用計數帶來的性能開銷。

在實際應用中,如果對性能要求較高,并且對象的所有權關系明確,不需要共享所有權,那么優先使用std::unique_ptr會是更好的選擇。std::unique_ptr的實現相對簡單,沒有引用計數的開銷,性能更高,內存占用也更小。只有在確實需要共享對象所有權的情況下,才使用std::shared_ptr,并且要注意避免不必要的復制和銷毀操作,以減少引用計數帶來的性能影響。

責任編輯:武曉燕 來源: 深度Linux
相關推薦

2025-07-01 02:25:00

2024-12-26 10:45:08

2010-02-05 14:36:20

C++智能指針

2023-11-17 11:48:08

智能指針C++

2010-12-17 10:07:59

2021-09-09 17:05:36

C++智能指針語言

2023-12-20 12:40:51

C++RAII編程

2024-03-01 16:43:48

C++11智能指針內存

2024-01-24 11:44:44

C++智能指針開發

2015-07-27 11:34:03

Linux內核指針

2025-08-11 01:00:00

2024-01-09 09:23:12

指針C++

2025-09-15 02:00:00

2010-01-27 14:18:41

Android智能指針

2021-08-11 09:01:48

智能指針Box

2025-04-22 03:00:00

2021-12-21 15:31:10

C++語言指針

2021-07-29 06:09:05

萬能指針C語言void

2011-07-01 14:28:47

Qt 指針

2011-04-11 11:09:50

this指針
點贊
收藏

51CTO技術棧公眾號

国产欧美va欧美不卡在线| 亚洲裸体俱乐部裸体舞表演av| 在线视频中文字幕一区二区| 日韩av一级大片| 91精东传媒理伦片在线观看| 国产精品va在线观看视色 | 久久精品一区二区| 国产综合色香蕉精品| 国产网址在线观看| 成人免费在线播放| 精品黑人一区二区三区久久 | 国内偷拍精品视频| 亚洲精品无吗| 欧美一区二区三区在线| 亚洲国产精品久久久久婷蜜芽| 日本韩国在线视频爽| proumb性欧美在线观看| 国产精品专区一| 色播视频在线播放| 久久精品亚洲人成影院| 亚洲精品日韩在线| 亚洲熟女一区二区三区| 99精品在免费线偷拍| 亚洲综合免费观看高清完整版在线| 人偷久久久久久久偷女厕| 不卡视频在线播放| 男人的天堂久久精品| 66m—66摸成人免费视频| 国产稀缺精品盗摄盗拍| 精品香蕉视频| 精品调教chinesegay| 少妇欧美激情一区二区三区| 成人在线观看免费视频| 午夜精品一区在线观看| 潘金莲一级淫片aaaaa免费看| 精品欧美不卡一区二区在线观看 | 日韩精品一二三区| 国语对白做受69| 中文字幕av免费在线观看| 日韩av在线播放网址| 亚洲精品电影在线观看| 精品人妻人人做人人爽夜夜爽| 欧美黄页在线免费观看| 欧洲av一区二区嗯嗯嗯啊| 国产h视频在线播放| 三级资源在线| 亚洲精品亚洲人成人网在线播放| 亚洲精品成人a8198a| 免费一级在线观看| 91一区二区三区在线观看| 国产免费高清一区| 亚洲国产成人一区二区| 国产一区不卡在线| 91精品综合视频| 91极品身材尤物theporn| 麻豆久久久久久久| 国产区精品在线观看| 曰批又黄又爽免费视频| 麻豆精品蜜桃视频网站| 国产精品自拍网| 97caocao| 国产精品一色哟哟哟| 91手机在线视频| 亚洲精品成人电影| 成人精品高清在线| 精品久久蜜桃| 麻豆app在线观看| 国产日韩欧美一区二区三区乱码 | av无码av天天av天天爽| 青青一区二区| 国产亚洲精品91在线| 美女100%露胸无遮挡| 日韩片欧美片| 欧美成人精品在线观看| 国产一级视频在线观看| 国产亚洲毛片在线| 国产suv精品一区二区| 中文亚洲av片在线观看| 国产一区二区三区蝌蚪| 国产精品播放| 男人久久精品| 中文字幕在线不卡一区二区三区| 黄色网在线视频| 热色播在线视频| 在线观看亚洲专区| 欧美丝袜在线观看| 黄色欧美网站| 中文字幕在线日韩| 久久精品一区二区三| 亚洲综合另类| 成人网在线观看| 欧美视频久久久| 国产婷婷一区二区| 免费观看国产视频在线| 爱草tv视频在线观看992| 日本久久电影网| 少妇欧美激情一区二区三区| 免费成人网www| 久久久999国产精品| 国产精品xxxx喷水欧美| 看电视剧不卡顿的网站| 国产精品入口免费| 大乳在线免费观看| 亚洲综合在线免费观看| 91淫黄看大片| 国产伦乱精品| 日韩亚洲一区二区| 一区二区三区福利视频| 国精产品一区一区三区mba视频| 精品不卡在线| caoporn97在线视频| 色国产精品一区在线观看| 97超碰人人看| 青青草成人影院| 91精品国产成人| 91久久极品少妇xxxxⅹ软件| 波多野结衣 久久| 国产精品夜夜嗨| 日韩欧美视频第二区| 亚洲羞羞网站| 欧美日韩国产系列| 欧美丰满少妇人妻精品| 欧美国产精品| 91精品久久久久久久久中文字幕 | 97久久超碰国产精品电影| 中文字幕中文字幕在线中心一区| 亚洲深夜视频| 精品国产凹凸成av人导航| 最新av电影网站| 日韩av中文在线观看| 国产一区二区三区av在线| 国产欧美黑人| 欧美人与z0zoxxxx视频| 少妇人妻好深好紧精品无码| 新67194成人永久网站| 豆国产97在线| 超碰免费在线播放| 欧美日韩不卡在线| 亚洲一二三精品| 日韩成人av影视| 日韩欧美精品在线不卡| 欧美黑人粗大| 亚洲人成毛片在线播放| 久久亚洲精品国产| 99精品一区二区三区| 国产精品入口芒果| 丁香五月缴情综合网| 久久99久久亚洲国产| aaa一区二区三区| 亚洲男人天堂av| 久久久国产精品久久久| 五月天综合网站| 川上优av一区二区线观看| 免费a级人成a大片在线观看| 欧美日韩成人综合| 动漫性做爰视频| 国产成人鲁色资源国产91色综| 日韩一级特黄毛片| 91综合精品国产丝袜长腿久久| 欧美人与性动交a欧美精品| 国产色综合视频| 一区二区日韩av| 秘密基地免费观看完整版中文| 国产精品啊啊啊| 国产精品有限公司| 忘忧草在线日韩www影院| 精品中文视频在线| 波多野结衣mp4| 亚洲欧洲www| 绯色av蜜臀vs少妇| 中文在线不卡| 日本视频精品一区| 91精品国产一区二区在线观看| 久久夜色精品国产| 不卡av中文字幕| 欧美性生交大片免网| 永久免费av无码网站性色av| 精品一区二区在线播放| 蜜臀av色欲a片无码精品一区| 久久精品论坛| 国产精品第3页| 免费看污视频的网站| 欧美国产一区视频在线观看| 伊人成人222| 精品99视频| 欧美最大成人综合网| 伊人久久大香伊蕉在人线观看热v| 美女少妇精品视频| 视频在线不卡| 一区二区视频在线| 熟妇人妻久久中文字幕| 日韩精品一二区| 国产精品自拍合集| 久久99免费视频| 2014国产精品| 性欧美gay| 欧美国产日产韩国视频| 国产二区在线播放| 精品久久一区二区| 国产精品午夜一区二区| 一区二区成人在线观看| 亚洲精品色午夜无码专区日韩| 国产一二精品视频| 国产精品亚洲αv天堂无码| 天天做天天爱综合| 欧美日本国产精品| av综合网站| 国产精品久久久久久久av电影 | 国产成人在线综合| 亚洲一区日本| 91免费版看片| 欧美一区二区三区高清视频| 国内精品国语自产拍在线观看| 欧美大陆国产| 国产精品99久久久久久久久 | 国产精品美乳在线观看| 91在线超碰| 美女av一区二区| 成年午夜在线| 亚洲精品福利在线| www.日韩高清| 欧美一区二区三区的| 中文字幕在线2018| 色综合久久综合| 日本一区二区网站| 亚洲一区在线观看免费观看电影高清| 欧美美女性生活视频| 久久精品在线免费观看| 一起草在线视频| 成人黄页在线观看| 少妇欧美激情一区二区三区| 国内精品国产三级国产a久久| 91色国产在线| 欧美专区18| 97国产在线播放| 99热免费精品| 国产资源在线视频| 亚洲国产国产亚洲一二三| 青青在线免费视频| 91成人影院| 男插女免费视频| 久久久久久美女精品| 一区二区三区偷拍| 色婷婷亚洲mv天堂mv在影片| 婷婷亚洲婷婷综合色香五月| 成人在线电影在线观看视频| 日韩伦理一区二区三区av在线| 久久不见久久见国语| 美乳视频一区二区| 欧美日韩电影免费看| 91成人在线播放| 亚洲电影观看| 全球成人中文在线| 欧美电影免费看| 国产精品久久久久久久av电影 | 国产精品v欧美精品∨日韩| 日韩08精品| 国产精品二区在线观看| 国产精品流白浆在线观看| 国产精品欧美久久| 日韩中出av| 日本精品一区| 久久国产精品亚洲人一区二区三区| 夜夜爽www精品| 欧美国产高潮xxxx1819| 蜜桃传媒一区二区三区| 亚洲欧美日本视频在线观看| 91淫黄看大片| 国产一区亚洲一区| 亚洲熟妇一区二区| 91一区在线观看| 99热99这里只有精品| 一区二区高清免费观看影视大全| 波多野结衣国产| 91成人看片片| 国产丝袜视频在线观看| 亚洲国产欧美一区二区三区同亚洲 | 欧美zozo另类异族| 午夜性色福利影院| 国产亚洲精品久久| 99自拍视频在线观看| 97在线精品国自产拍中文| 国产另类xxxxhd高清| 国产在线视频一区| 激情小说一区| 亚洲啪啪av| 国自产拍偷拍福利精品免费一| av观看免费在线| 激情六月婷婷久久| 在线免费播放av| 国产精品毛片高清在线完整版 | 欧美主播一区二区三区美女| 国产精品人人爽| 亚洲免费视频观看| 宅男网站在线免费观看| 日本视频久久久| 日韩视频1区| 欧日韩一区二区三区| 狠久久av成人天堂| 欧美成人三级在线播放| caoporm超碰国产精品| 中文字幕求饶的少妇| 欧美视频一二三| www.天堂在线| 中文字幕欧美日韩精品| 国产福利片在线观看| 成人夜晚看av| 欧美精品色图| 国产精品入口芒果| 国产老女人精品毛片久久| 久久精品—区二区三区舞蹈| 亚洲va韩国va欧美va精品| 一级片视频播放| 亚洲人高潮女人毛茸茸| caoprom在线| 亚洲在线第一页| 日韩国产欧美一区二区| 免费在线观看毛片网站| 成人丝袜视频网| 久久中文免费视频| 欧美日韩美女一区二区| 久久经典视频| 91国内免费在线视频| www.久久东京| 亚洲春色综合另类校园电影| 久久国产日韩| 欧美高清性xxxx| 岛国av一区二区| 色婷婷av一区二区三| 欧美精品免费播放| 成人动漫视频在线观看| 一区二区三区精品国产| 日本中文字幕一区二区视频| 欧美成人午夜精品免费| 精品久久久久久久久久久久久久 | 91啪亚洲精品| www.日本精品| 亚洲精品美女网站| 国产精品yjizz视频网| 国产精品视频入口| 亚洲手机视频| 中文字幕视频观看| 亚洲五码中文字幕| 韩国av免费在线观看| 久久久久久18| 精品三级av| 国产精品12345| av午夜一区麻豆| 国产福利拍拍拍| 国产视频久久久久久久| 综合毛片免费视频| 欧美精品与人动性物交免费看| 国产精品毛片| 免费看黄色的视频| 欧美亚洲一区二区在线| 在线免费观看黄| 成人乱人伦精品视频在线观看| 亚洲成人精品| 久久久久亚洲av片无码v| 亚洲国产一区二区视频| 污视频网站免费观看| 欧美一级在线播放| 欧洲三级视频| 日本高清一区二区视频| 亚洲精品国久久99热| 色一情一乱一区二区三区| 欧美性视频网站| 色97色成人| 中文字幕 欧美 日韩| 午夜电影一区二区三区| 九色国产在线观看| 成人精品一区二区三区| 黄色成人91| 六月婷婷七月丁香| 欧美日韩1区2区| 暧暧视频在线免费观看| 日产精品高清视频免费| 国产在线播放一区二区三区 | 欧美色爱综合网| 在线āv视频| 欧美大香线蕉线伊人久久国产精品| 全部av―极品视觉盛宴亚洲| 538任你躁在线精品视频网站| 亚洲国产精品成人va在线观看| 亚州一区二区三区| 中国一级大黄大黄大色毛片| 91麻豆高清视频| 国产色综合视频| 国产97在线观看| 午夜激情一区| 国产123在线| 精品久久人人做人人爱| 日韩视频网站在线观看| 日本aa在线观看| 欧美激情一区二区三区在线| www.97av| 国产精品视频自拍| 亚洲一区观看| 18岁成人毛片| 在线观看视频亚洲|