偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

我用 shared_ptr 踩了三年坑,終于明白 Google 為什么推薦 unique_ptr

開發(fā)
智能指針的"智能"不在于功能有多復(fù)雜,而在于設(shè)計思路的清晰和使用場景的準(zhǔn)確。就像寫代碼一樣,簡單往往比復(fù)雜更難,但也更有價值。

大家好,我是小康。

最近在技術(shù)群里看到一個有趣的討論,一個小伙伴問:"為什么Google的代碼規(guī)范總是推薦用unique_ptr,對shared_ptr卻很謹(jǐn)慎?是不是shared_ptr有什么坑?"

這個問題瞬間炸出了一群技術(shù)大佬,有人說shared_ptr性能差,有人說容易內(nèi)存泄漏,還有人說設(shè)計思路就不對...

說實話,我剛開始學(xué)C++的時候也很困惑。明明shared_ptr看起來更"智能"啊,可以自動管理引用計數(shù),多個對象可以共享,聽起來就很高級。而unique_ptr好像就是個"獨(dú)占狂",一個對象只能有一個主人,顯得很"小氣"。

但是工作幾年后,我終于明白了其中的門道。今天就來給大家深度剖析一下這兩個"智能指針兄弟"的恩怨情仇。

一、先說說這兩兄弟的"出身"

1. unique_ptr:獨(dú)占型的"專一男友"

unique_ptr就像那種專一的男朋友,一旦認(rèn)定了一個對象,就獨(dú)占所有權(quán),絕不與其他人分享。

std::unique_ptr<int> ptr1(new int(42));
// std::unique_ptr<int> ptr2 = ptr1;  // 編譯錯誤!不能復(fù)制
std::unique_ptr<int> ptr2 = std::move(ptr1);  // 只能轉(zhuǎn)移所有權(quán)
// 現(xiàn)在ptr1為空,ptr2擁有對象

2. shared_ptr:共享型的"中央空調(diào)"

shared_ptr則像中央空調(diào),可以同時服務(wù)多個"用戶",內(nèi)部維護(hù)一個引用計數(shù)器。

std::shared_ptr<int> ptr1(new int(42));
std::shared_ptr<int> ptr2 = ptr1;  // 可以復(fù)制,引用計數(shù)變?yōu)?
std::shared_ptr<int> ptr3 = ptr1;  // 引用計數(shù)變?yōu)?
// 當(dāng)所有shared_ptr都銷毀后,對象才會被釋放

二、為什么Google偏愛unique_ptr?

1. 性能差距:不是一個量級的

我們先來看看性能對比,數(shù)據(jù)會說話:

// 性能測試代碼
#include <chrono>
#include <memory>

// unique_ptr創(chuàng)建和銷毀
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; ++i) {
    auto ptr = std::make_unique<int>(i);
}  // 自動銷毀
auto end = std::chrono::high_resolution_clock::now();
std::cout << "unique_ptr用時: " << 
    std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() 
    << "微秒" << std::endl;

// shared_ptr創(chuàng)建和銷毀
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; ++i) {
    auto ptr = std::make_shared<int>(i);
}  // 自動銷毀
end = std::chrono::high_resolution_clock::now();
std::cout << "shared_ptr用時: " << 
    std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() 
    << "微秒" << std::endl;

在我的機(jī)器上測試結(jié)果:

  • unique_ptr:約20072微秒
  • shared_ptr:約60081微秒

shared_ptr竟然比unique_ptr慢了3倍多!

為什么會這樣?因為shared_ptr需要:

  • 維護(hù)引用計數(shù)(原子操作,線程安全但開銷大)
  • 額外的內(nèi)存分配(控制塊)
  • 復(fù)制時需要原子遞增
  • 銷毀時需要原子遞減并檢查是否為0

2. 內(nèi)存開銷:shared_ptr是個"大胃王"

std::cout << "unique_ptr大小: " << sizeof(std::unique_ptr<int>) << "字節(jié)" << std::endl;
std::cout << "shared_ptr大小: " << sizeof(std::shared_ptr<int>) << "字節(jié)" << std::endl;

結(jié)果:

  • unique_ptr:8字節(jié)(就是一個普通指針)
  • shared_ptr:16字節(jié)(指針+控制塊指針)

而且shared_ptr還會額外分配一個控制塊,存儲引用計數(shù)等信息,至少需要額外的16個字節(jié)。

如果你的程序中有大量的智能指針,這個差距就很明顯了。

3. 設(shè)計哲學(xué):ownership要清晰

Google的代碼規(guī)范有一個重要原則:所有權(quán)要清晰。

看這個例子:

// 不好的設(shè)計:所有權(quán)不明確
class DataProcessor {
    std::shared_ptr<Data> data_;
public:
    void setData(std::shared_ptr<Data> data) { data_ = data; }
};

class DataCache {
    std::shared_ptr<Data> cached_data_;
public:
    void cache(std::shared_ptr<Data> data) { cached_data_ = data; }
};

// 使用時:誰擁有data?誰負(fù)責(zé)生命周期?不清楚!
auto data = std::make_shared<Data>();
processor.setData(data);
cache.cache(data);
// 好的設(shè)計:所有權(quán)清晰
class DataManager {
    std::unique_ptr<Data> data_;  // 明確的擁有者
public:
    Data* getData() { return data_.get(); }  // 只提供訪問權(quán)
    void setData(std::unique_ptr<Data> data) { 
        data_ = std::move(data); 
    }
};

// 使用時:DataManager明確擁有data
auto data = std::make_unique<Data>();
manager.setData(std::move(data));

三、shared_ptr的"隱形炸彈"

1. 循環(huán)引用:程序員的噩夢

這是shared_ptr最著名的坑:

class Parent;
class Child;

class Parent {
public:
    std::shared_ptr<Child> child_;
    ~Parent() { std::cout << "Parent析構(gòu)" << std::endl; }
};

class Child {
public:
    std::shared_ptr<Parent> parent_;  // 危險!循環(huán)引用
    ~Child() { std::cout << "Child析構(gòu)" << std::endl; }
};

{
    auto parent = std::make_shared<Parent>();
    auto child = std::make_shared<Child>();
    
    parent->child_ = child;
    child->parent_ = parent;  // 形成循環(huán)引用
}
// 程序結(jié)束,但你會發(fā)現(xiàn)析構(gòu)函數(shù)都沒被調(diào)用!
// 內(nèi)存泄漏了!

這種bug特別隱蔽,很難調(diào)試。而unique_ptr從設(shè)計上就避免了這個問題。

2. 線程安全的假象

很多人以為shared_ptr是線程安全的,其實只是引用計數(shù)的操作是線程安全的,對象本身的訪問并不安全:

std::shared_ptr<std::vector<int>> ptr = std::make_shared<std::vector<int>>();

// 這些操作是線程安全的:
std::shared_ptr<std::vector<int>> ptr2 = ptr;  // 拷貝shared_ptr
ptr.reset();  // 重置shared_ptr
auto count = ptr.use_count();  // 查看引用計數(shù)

// 這些操作不是線程安全的:
// 線程1
ptr->push_back(1);  // 不安全! 修改vector內(nèi)容

// 線程2  
ptr->push_back(2);  // 不安全!

// 引用計數(shù)的增減是安全的,但對vector的操作不安全

3. 性能陷阱:不經(jīng)意的拷貝

// 看似無害的代碼
void processData(std::shared_ptr<Data> data) {  // 注意:按值傳遞
    // 處理數(shù)據(jù)...
}  // 函數(shù)結(jié)束時,局部的shared_ptr被銷毀,引用計數(shù)-1

// 每次調(diào)用都會發(fā)生什么?
auto data = std::make_shared<Data>();  // 引用計數(shù)=1
for (int i = 0; i < 1000000; ++i) {
    processData(data);  // 1. 拷貝shared_ptr,引用計數(shù)+1(原子操作)
                        // 2. 函數(shù)執(zhí)行
                        // 3. 函數(shù)結(jié)束,引用計數(shù)-1(原子操作)
}
// 100萬次原子操作的開銷!

應(yīng)該改為:

// 方案1:按引用傳遞shared_ptr(推薦)
void processData(const std::shared_ptr<Data>& data) {
    // 處理數(shù)據(jù),無拷貝開銷
}

// 方案2:傳遞原始指針(如果不需要延長生命周期)
void processData(Data* data) {
    // 處理數(shù)據(jù)...
}

// 方案3:傳遞引用(如果確定對象存在)
void processData(const Data& data) {
    // 處理數(shù)據(jù)...
}

四、什么時候才用shared_ptr?

雖然我們一直在"黑"shared_ptr,但它確實有適用場景,關(guān)鍵是要真正需要共享所有權(quán):

(1) 緩存系統(tǒng):多個持有者,不確定誰先釋放

class ResourceCache {
    std::unordered_map<std::string, std::shared_ptr<ExpensiveResource>> cache_;
public:
    std::shared_ptr<ExpensiveResource> get(const std::string& key) {
        if (cache_.find(key) == cache_.end()) {
            cache_[key] = std::make_shared<ExpensiveResource>(key);
        }
        return cache_[key];  // 調(diào)用者和緩存都持有,誰都可能先釋放
    }
    
    void cleanup() {
        // 清理緩存,但如果外部還在使用,對象不會被銷毀
        cache_.clear();
    }
};

(2) 異步編程:延長對象生命周期

class DataProcessor {
public:
    void processAsync(std::shared_ptr<Data> data) {
        // 啟動異步任務(wù),data的生命周期不確定
        std::thread([data]() {
            std::this_thread::sleep_for(std::chrono::seconds(5));
            // 即使調(diào)用方已經(jīng)結(jié)束,data依然有效
            processData(*data);
        }).detach();
    }
    
    // 如果用unique_ptr會怎樣?
    void processBad(std::unique_ptr<Data> data) {
        std::thread([&data]() {  // 危險!引用可能懸空
            processData(*data);   // 可能崩潰
        }).detach();
    }
};

(3) 資源池:共享昂貴資源

class DatabaseConnectionPool {
    std::vector<std::shared_ptr<Connection>> pool_;
public:
    std::shared_ptr<Connection> getConnection() {
        if (!pool_.empty()) {
            auto conn = pool_.back();
            pool_.pop_back();
            return conn;  // 多個客戶端可能同時使用同一連接
        }
        returnstd::make_shared<Connection>();
    }
    
    void returnConnection(std::shared_ptr<Connection> conn) {
        // 簡單地放回池中,shared_ptr會自動管理生命周期
        // 即使有其他地方還在使用這個連接,也沒關(guān)系
        // 當(dāng)所有引用都釋放后,連接會自動銷毀
        pool_.push_back(conn);
    }
};

(4) 插件系統(tǒng):多模塊共享

class PluginManager {
    std::unordered_map<std::string, std::shared_ptr<Plugin>> plugins_;
public:
    std::shared_ptr<Plugin> loadPlugin(const std::string& name) {
        if (plugins_.find(name) == plugins_.end()) {
            plugins_[name] = std::make_shared<Plugin>(name);
        }
        return plugins_[name];  // 多個模塊可能同時需要同一個插件
    }
};

// 使用場景
auto audioPlugin = pluginManager.loadPlugin("audio");
auto videoPlugin = pluginManager.loadPlugin("audio");  // 返回同一個實例
// audioPlugin和videoPlugin指向同一對象,任何一個都可以安全使用

五、實戰(zhàn)指南:如何選擇智能指針?

遵循這個簡單的決策流程:

第一步:默認(rèn)選擇unique_ptr

除非有特殊需求,否則總是從unique_ptr開始。它性能最好,語義最清晰。

第二步:遇到問題時再考慮升級

(1) 需要轉(zhuǎn)移所有權(quán)? → 繼續(xù)用unique_ptr + std::move

(2) 需要多個擁有者? → 問自己三個問題:

  • 是否真的有多個"擁有者"需要這個對象?
  • 這些擁有者的生命周期是否不確定?
  • 任何一個擁有者都不應(yīng)該單獨(dú)決定對象何時銷毀?

如果三個答案都是"是",才用shared_ptr

(3) 可能有循環(huán)引用? → 用weak_ptr打破循環(huán)

六、總結(jié):智能指針的"智慧"選擇

Google推薦優(yōu)先使用unique_ptr不是沒有道理的:

  • 性能更好:沒有引用計數(shù)開銷
  • 內(nèi)存更?。褐挥幸粋€指針的大小
  • 設(shè)計更清晰:明確的所有權(quán)語義
  • 更安全:避免循環(huán)引用等陷阱

記住這個原則:能用unique_ptr就不用shared_ptr,能用引用就不用指針。

最后想說,智能指針的"智能"不在于功能有多復(fù)雜,而在于設(shè)計思路的清晰和使用場景的準(zhǔn)確。就像寫代碼一樣,簡單往往比復(fù)雜更難,但也更有價值。

責(zé)任編輯:趙寧寧 來源: 跟著小康學(xué)編程
相關(guān)推薦

2025-05-28 08:50:00

C++循環(huán)引用節(jié)點

2025-04-29 08:35:00

2025-05-22 10:10:00

C++循環(huán)引用開發(fā)

2019-09-09 08:28:48

互聯(lián)網(wǎng)數(shù)據(jù)磁盤

2019-10-30 15:35:47

Android谷歌手機(jī)

2019-08-28 16:38:49

finalJava編程語言

2010-11-12 09:51:43

Android

2020-04-25 20:20:28

蘋果庫克手機(jī)

2025-02-26 01:23:02

C++11Raw代碼

2020-09-03 07:55:02

并發(fā)操作系統(tǒng)Java

2011-12-23 10:23:45

GoogleMozilla

2022-05-17 14:28:42

編程語言Julia代碼

2021-02-18 07:55:27

數(shù)據(jù)湖存儲數(shù)據(jù)

2024-07-02 17:43:26

2019-09-20 13:16:22

手機(jī)攝像頭三攝

2022-06-28 22:27:38

數(shù)字化轉(zhuǎn)型

2022-01-09 23:44:14

5G4G手機(jī)

2022-11-27 17:21:04

ClickHouseJDBC函數(shù)

2025-06-27 09:11:08

2013-07-17 09:13:19

點贊
收藏

51CTO技術(shù)棧公眾號