字節(jié)跳動C++二面:手寫shared_ptr實(shí)現(xiàn)
在 C++ 開發(fā)領(lǐng)域,智能指針是一項(xiàng)極為重要的機(jī)制,它極大地提升了內(nèi)存管理的安全性與便捷性。其中,shared_ptr作為智能指針家族的關(guān)鍵成員,采用引用計數(shù)的方式,允許多個指針共享同一對象的所有權(quán),當(dāng)最后一個指向?qū)ο蟮膕hared_ptr被銷毀時,對象所占用的內(nèi)存會被自動釋放,有效規(guī)避了內(nèi)存泄漏的風(fēng)險。
在字節(jié)跳動 C++ 二面中,要求手寫shared_ptr實(shí)現(xiàn),這一題目旨在深度考察面試者對 C++ 內(nèi)存管理、對象生命周期把控、模板編程以及運(yùn)算符重載等多方面核心知識的理解與運(yùn)用能力。要想成功完成這一挑戰(zhàn),面試者不僅需要清晰掌握shared_ptr的底層運(yùn)行原理,還得能夠用嚴(yán)謹(jǐn)、高效且符合 C++ 最佳實(shí)踐的代碼將其準(zhǔn)確呈現(xiàn)出來。接下來,我們就一同深入剖析如何逐步實(shí)現(xiàn)一個簡易版的shared_ptr 。
Part1.什么是shared_ptr?
在 C++ 編程的世界中,內(nèi)存管理一直是一個讓人又愛又恨的話題。手動管理內(nèi)存就像是在走鋼絲,稍有不慎就會引發(fā)內(nèi)存泄漏、懸空指針等一系列嚴(yán)重的問題,讓程序變得脆弱不堪。為了解決這些棘手的問題,C++11 引入了智能指針這個強(qiáng)大的工具,而std::shared_ptr就是其中的佼佼者。
std:shared_ptr是 C++ 標(biāo)準(zhǔn)庫提供的一種智能指針,它采用了引用計數(shù)的機(jī)制,允許多個指針共享同一個對象的所有權(quán) ,這就好比多個小伙伴共同擁有一個玩具,每個人都對這個玩具有一定的使用權(quán)。當(dāng)最后一個擁有這個玩具的小伙伴不再需要它時(即引用計數(shù)為 0),玩具就會被自動回收,這樣就避免了手動管理內(nèi)存時可能出現(xiàn)的各種麻煩。
例如,在一個圖形渲染引擎中,有多個模塊可能需要訪問同一個紋理資源。使用std::shared_ptr,可以讓這些模塊共享對紋理資源的引用,而不必?fù)?dān)心資源的釋放問題。當(dāng)所有模塊都不再使用該紋理時,std::shared_ptr會自動釋放紋理占用的內(nèi)存,大大提高了內(nèi)存管理的安全性和效率。
在實(shí)際使用中,std::shared_ptr的操作非常靈活。比如,我們可以通過std::make_shared函數(shù)來創(chuàng)建一個std::shared_ptr對象,這種方式不僅更加簡潔,而且效率更高,因?yàn)樗淮涡苑峙淞藢ο蠛鸵糜嫈?shù)所需的內(nèi)存。同時,std::shared_ptr還支持賦值、比較等操作,使得代碼的編寫更加自然和直觀。
1.1 產(chǎn)生的原因
shared_ptr的產(chǎn)生與unique_ptr類似,都是為了解決raw pointer的new和delete的成對使用,導(dǎo)致的野指針、內(nèi)存泄漏、重復(fù)釋放內(nèi)存等。
不過shared_ptr與unique_ptr場景又有所不同,這里主要是一個raw pointer在不同的代碼塊之間傳來傳去的場景,或者指針指向的內(nèi)存比較大,這段內(nèi)存可以切分成很多小部分,但是他們卻需要共享彼此的數(shù)據(jù)。
1.2 shared_ptr特性
shared_ptr 有兩個特性:
- 特性1: 對raw pointer進(jìn)行了一層封裝,讓C++程序員不用再擔(dān)心何時去釋放分配好的內(nèi)存。
- 特性2: 共享,使用shared_ptr的指針可以共享同一塊內(nèi)存中的數(shù)據(jù)。
思想是:該類型智能指針在實(shí)現(xiàn)上采用的是引用計數(shù)機(jī)制,即便有一個 shared_ptr 指針放棄了堆內(nèi)存的“使用權(quán)”(引用計數(shù)減 1),也不會影響其他指向同一堆內(nèi)存的 shared_ptr 指針(只有引用計數(shù)為 0 時,堆內(nèi)存才會被自動釋放)。
1.3 shared_ptr的優(yōu)勢
(1)自動釋放:當(dāng)最后一個std::shared_ptr離開作用域時,引用計數(shù)變?yōu)榱悖鼤詣诱{(diào)用對象的析構(gòu)函數(shù),防止內(nèi)存泄漏 。在一個網(wǎng)絡(luò)通信模塊中,我們可能會創(chuàng)建一個std::shared_ptr來管理一個連接對象。當(dāng)所有與該連接相關(guān)的操作完成,并且指向該連接對象的std::shared_ptr都超出作用域時,連接對象會被自動釋放,無需手動調(diào)用析構(gòu)函數(shù)。這樣一來,我們就不用擔(dān)心因?yàn)槭韬龆鴮?dǎo)致連接資源沒有被正確釋放,從而大大提高了代碼的可靠性。
#include <iostream>
#include <memory>
// 模擬網(wǎng)絡(luò)連接對象
class Connection {
public:
Connection() { std::cout << "Connection established\n"; }
~Connection() { std::cout << "Connection released\n"; }
void send(const std::string& data) {
std::cout << "Sending data: " << data << "\n";
}
};
// 模擬網(wǎng)絡(luò)通信模塊
void handleConnection() {
// 創(chuàng)建 shared_ptr 管理連接對象(引用計數(shù)=1)
auto conn = std::make_shared<Connection>();
// 模擬多個操作共享該連接
auto task1 = [conn]() { conn->send("Task1 data"); };
auto task2 = [conn]() { conn->send("Task2 data"); }; // 引用計數(shù)增加到3
task1();
task2();
// task1/task2的lambda析構(gòu),引用計數(shù)減回1
} // conn離開作用域,引用計數(shù)歸零,自動調(diào)用Connection析構(gòu)
int main() {
handleConnection();
// 輸出示例:
// Connection established
// Sending data: Task1 data
// Sending data: Task2 data
// Connection released
return 0;
}(2)對象共享:多個std::shared_ptr可以指向同一對象,這使得資源共享的實(shí)現(xiàn)變得更加簡單。在一個游戲開發(fā)項(xiàng)目中,多個游戲?qū)ο罂赡苄枰蚕硗粋€紋理資源。通過std::shared_ptr,我們可以輕松地讓這些游戲?qū)ο蠊蚕韺y理的引用,而不必為每個對象單獨(dú)復(fù)制紋理數(shù)據(jù),節(jié)省了內(nèi)存空間,同時也提高了資源的利用率。
#include <iostream>
#include <memory>
#include <vector>
// 模擬紋理資源類
class Texture {
public:
Texture(const std::string& path) : m_path(path) {
std::cout << "Loaded texture: " << m_path << "\n";
}
~Texture() { std::cout << "Unloaded texture: " << m_path << "\n"; }
void render(int x, int y) const {
std::cout << "Rendering texture at (" << x << ", " << y
<< ") from: " << m_path << "\n";
}
private:
std::string m_path;
};
// 游戲?qū)ο蠡悾ㄊ褂霉蚕砑y理)
class GameObject {
public:
GameObject(std::shared_ptr<Texture> texture) : m_texture(texture) {}
virtual void draw() = 0;
protected:
std::shared_ptr<Texture> m_texture; // 共享紋理的智能指針
};
// 具體游戲?qū)ο螅航巧?class Character : public GameObject {
public:
using GameObject::GameObject;
void draw() override {
m_texture->render(100, 200); // 使用共享紋理
std::cout << "Character drawn\n";
}
};
// 具體游戲?qū)ο螅簣鼍氨尘?class Background : public GameObject {
public:
using GameObject::GameObject;
void draw() override {
m_texture->render(0, 0); // 使用同一份紋理(可能縮放或裁剪)
std::cout << "Background drawn\n";
}
};
int main() {
// 1. 加載紋理(僅一次)
auto sharedTexture = std::make_shared<Texture>("assets/hero.png");
// 2. 創(chuàng)建多個共享該紋理的游戲?qū)ο? Character hero(sharedTexture);
Background scene(sharedTexture);
// 3. 渲染時無需關(guān)心紋理生命周期
hero.draw();
scene.draw();
// 4. main結(jié)束時,所有對象析構(gòu)后引用計數(shù)歸零,自動釋放紋理
return 0;
}(3)異常安全:std::shared_ptr的引用計數(shù)會自動管理,不會因?yàn)楹瘮?shù)異常退出而泄漏內(nèi)存。在進(jìn)行復(fù)雜的數(shù)據(jù)庫操作時,可能會涉及多個步驟,其中任何一步都有可能拋出異常。如果我們使用std::shared_ptr來管理數(shù)據(jù)庫連接對象,即使在操作過程中發(fā)生異常,std::shared_ptr也會確保連接對象被正確釋放,避免了內(nèi)存泄漏和資源懸空的問題,保證了程序的健壯性。
#include <iostream>
#include <memory>
#include <stdexcept>
// 模擬數(shù)據(jù)庫連接類
class DatabaseConnection {
public:
DatabaseConnection(const std::string& url) : m_url(url) {
std::cout << "Connected to database: " << m_url << "\n";
}
~DatabaseConnection() {
std::cout << "Disconnected from database: " << m_url << "\n";
}
void executeQuery(const std::string& sql) {
if (sql.empty()) throw std::runtime_error("Empty SQL query");
std::cout << "Executed: " << sql << "\n";
}
private:
std::string m_url;
};
// 高風(fēng)險操作:可能拋出異常的復(fù)雜數(shù)據(jù)庫事務(wù)
void riskyDatabaseOperation(std::shared_ptr<DatabaseConnection> conn) {
conn->executeQuery("BEGIN TRANSACTION"); // 步驟1:開始事務(wù)
// 模擬可能失敗的操作(如違反約束)
conn->executeQuery("UPDATE users SET balance = -100 WHERE id = 123"); // 步驟2:可能拋異常
conn->executeQuery("COMMIT"); // 步驟3:提交事務(wù)(若異常則不會執(zhí)行)
}
int main() {
try {
// 1. 創(chuàng)建共享的數(shù)據(jù)庫連接
auto dbConn = std::make_shared<DatabaseConnection>("mysql://localhost:3306");
// 2. 執(zhí)行高風(fēng)險操作(即使內(nèi)部拋出異常,conn也會被釋放)
riskyDatabaseOperation(dbConn);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
// 無需手動釋放dbConn!shared_ptr會在棧展開時自動析構(gòu)
}
return 0;
}Part2.shared_ptr的使用方法
2.1 創(chuàng)建 shared_ptr 對象
在 C++ 中,創(chuàng)建std::shared_ptr對象主要有兩種常見方式。一種是使用std::make_shared函數(shù) ,它能一次性分配對象和控制塊的內(nèi)存,效率較高,語法也更為簡潔。比如創(chuàng)建一個指向int類型對象的std::shared_ptr,可以這樣寫:
auto ptr1 = std::make_shared<int>(42);這就創(chuàng)建了一個std::shared_ptr,它指向一個值為 42 的int對象。
另一種方式是使用原始指針來構(gòu)造std::shared_ptr,但這種方式需要注意原始指針必須是通過new分配的,否則會導(dǎo)致未定義行為 。示例如下:
int* rawPtr = new int(10);
std::shared_ptr<int> ptr2(rawPtr);這里先創(chuàng)建了一個原始指針rawPtr,然后用它構(gòu)造了std::shared_ptr對象ptr2。不過這種方式相對繁瑣,且容易出錯,因?yàn)樾枰謩庸芾碓贾羔樀纳芷冢愿扑]使用std::make_shared。
2.2 引用計數(shù)相關(guān)操作
std::shared_ptr的核心特性之一就是引用計數(shù),通過一些成員函數(shù),我們可以對引用計數(shù)進(jìn)行操作和查詢。use_count函數(shù)用于返回當(dāng)前有多少個std::shared_ptr指向同一個對象,主要用于調(diào)試目的 。例如:
auto ptr3 = std::make_shared<int>(100);
std::cout << "ptr3的引用計數(shù): " << ptr3.use_count() << std::endl;
auto ptr4 = ptr3;
std::cout << "賦值后ptr3的引用計數(shù): " << ptr3.use_count() << std::endl;在上述代碼中,首先創(chuàng)建ptr3時,其引用計數(shù)為 1;當(dāng)把ptr3賦值給ptr4后,它們指向同一個對象,引用計數(shù)變?yōu)?2。
unique函數(shù)則用于判斷當(dāng)前std::shared_ptr是否是指向?qū)ο蟮奈ㄒ恢羔?,如果是則返回true,否則返回false。接著上面的代碼:
if (ptr3.unique()) {
std::cout << "ptr3是唯一指向?qū)ο蟮闹羔? << std::endl;
} else {
std::cout << "ptr3不是唯一指向?qū)ο蟮闹羔? << std::endl;
}由于ptr3和ptr4共享對象,所以ptr3.unique()會返回false。
reset函數(shù)用于重置std::shared_ptr,它有兩種常見用法。不帶參數(shù)調(diào)用reset時,會減少引用計數(shù),如果引用計數(shù)變?yōu)?0,就會釋放所指向的對象,并將當(dāng)前std::shared_ptr置為空 。例如:
ptr3.reset();
std::cout << "reset后ptr3的引用計數(shù): " << ptr3.use_count() << std::endl;此時ptr3的引用計數(shù)變?yōu)?0(如果沒有其他指向該對象的std::shared_ptr),對象被釋放,ptr3變?yōu)榭罩羔槨?/span>
帶參數(shù)調(diào)用reset時,會先減少原對象的引用計數(shù),然后讓std::shared_ptr指向新的對象 。例如:
ptr4.reset(new int(200));
std::cout << "ptr4指向新對象后的引用計數(shù): " << ptr4.use_count() << std::endl;這里ptr4先減少對原對象的引用計數(shù),然后指向一個新的值為 200 的int對象,引用計數(shù)變?yōu)?1。
2.3 作為普通指針使用
std::shared_ptr重載了*和->操作符,這使得它可以像普通指針一樣使用 。通過*操作符可以解引用std::shared_ptr,訪問其所指向的對象的值。例如:
auto ptr5 = std::make_shared<int>(50);
int value = *ptr5;
std::cout << "ptr5指向的值: " << value << std::endl;這里通過*ptr5獲取到ptr5指向的int對象的值,并賦值給value。
而->操作符則用于訪問所指向?qū)ο蟮某蓡T函數(shù)或成員變量。假設(shè)有一個自定義類MyClass:
class MyClass {
public:
void print() {
std::cout << "這是MyClass的成員函數(shù)" << std::endl;
}
};
auto ptr6 = std::make_shared<MyClass>();
ptr6->print();在這段代碼中,通過ptr6->print()調(diào)用了MyClass對象的print成員函數(shù),就如同使用普通指針一樣方便 。這種重載操作符的設(shè)計,使得std::shared_ptr在使用上更加直觀和自然,無需額外的函數(shù)調(diào)用就能完成對對象的操作。
Part3.循環(huán)引用問題及解決
3.1 循環(huán)引用示例
雖然std::shared_ptr在內(nèi)存管理方面表現(xiàn)出色,但在使用過程中,如果不注意其引用計數(shù)機(jī)制,就可能會遇到循環(huán)引用的問題 。循環(huán)引用是指兩個或多個對象相互引用對方的std::shared_ptr,從而導(dǎo)致引用計數(shù)永遠(yuǎn)無法歸零,最終造成內(nèi)存泄漏。
下面是一個具體的代碼示例,展示了循環(huán)引用是如何發(fā)生的:
#include <iostream>
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() {
std::cout << "A destroyed" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() {
std::cout << "B destroyed" << std::endl;
}
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
return 0;
}在上述代碼中,A類和B類相互持有對方的std::shared_ptr。當(dāng)main函數(shù)結(jié)束時,a和b的局部變量被銷毀,它們的引用計數(shù)會減 1,但由于a的b_ptr指向b,b的a_ptr指向a,所以a和b的引用計數(shù)始終無法降為 0 ,導(dǎo)致A和B的析構(gòu)函數(shù)都不會被調(diào)用,內(nèi)存無法釋放,從而發(fā)生內(nèi)存泄漏。這種循環(huán)引用的問題在實(shí)際項(xiàng)目中可能很難被發(fā)現(xiàn),尤其是在復(fù)雜的對象關(guān)系中,它會逐漸消耗系統(tǒng)資源,影響程序的性能和穩(wěn)定性 。
3.2 使用 std::weak_ptr 打破循環(huán)引用
為了解決std::shared_ptr的循環(huán)引用問題,C++ 引入了std::weak_ptr 。std::weak_ptr是一種弱引用智能指針,它不擁有所指向?qū)ο蟮乃袡?quán),不會增加對象的引用計數(shù),主要用于解決std::shared_ptr循環(huán)引用的問題,同時提供一種安全的方式來訪問std::shared_ptr所管理的對象 。
std::weak_ptr的核心特性之一是它的lock方法,該方法用于嘗試獲取一個指向所引用對象的std::shared_ptr 。如果對象已經(jīng)被釋放,lock方法會返回一個空的std::shared_ptr,通過這種方式可以安全地檢查對象是否仍然有效,避免懸空指針的問題。
下面是使用std::weak_ptr解決上述循環(huán)引用問題的代碼示例:
#include <iostream>
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() {
std::cout << "A destroyed" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> a_weak;
~B() {
std::cout << "B destroyed" << std::endl;
}
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_weak = a;
return 0;
}在這個改進(jìn)后的代碼中,B類不再持有A類的std::shared_ptr,而是使用std::weak_ptr來引用A 。當(dāng)main函數(shù)結(jié)束時,a的引用計數(shù)會因?yàn)榫植孔兞縜的銷毀而減為 0,A對象被正確釋放;接著,b的引用計數(shù)也會減為 0,B對象也被釋放,從而避免了循環(huán)引用導(dǎo)致的內(nèi)存泄漏問題 。
當(dāng)需要通過std::weak_ptr訪問對象時,可以使用lock方法,例如:
class B {
public:
std::weak_ptr<A> a_weak;
void accessA() {
auto temp = a_weak.lock();
if (temp) {
// 安全地訪問A對象
temp->someFunction();
} else {
std::cout << "A對象已被釋放" << std::endl;
}
}
~B() {
std::cout << "B destroyed" << std::endl;
}
};在上述代碼的accessA方法中,先通過lock方法獲取A對象的std::shared_ptr,并檢查其是否為空 。如果不為空,就可以安全地訪問A對象的成員;如果為空,說明A對象已經(jīng)被釋放,避免了懸空指針的風(fēng)險。
Part4.shared_ptr的線程安全性
4.1 引用計數(shù)的線程安全
在多線程環(huán)境下,std::shared_ptr的引用計數(shù)操作是線程安全的,這是它的一個重要特性 。當(dāng)多個線程同時對同一個std::shared_ptr進(jìn)行復(fù)制、賦值或銷毀等操作時,其引用計數(shù)的遞增和遞減是通過原子操作來實(shí)現(xiàn)的,無需額外的加鎖機(jī)制 。這就好比有一個公共的計數(shù)器,多個線程可以同時對它進(jìn)行增加或減少操作,而且不會出現(xiàn)計數(shù)錯誤的情況。
例如,在一個多線程的文件處理系統(tǒng)中,多個線程可能同時讀取或處理同一個文件對象,每個線程都持有一個std::shared_ptr指向該文件對象 。當(dāng)某個線程完成對文件的處理,其持有的std::shared_ptr離開作用域時,引用計數(shù)會自動減 1,這個過程是線程安全的,不會因?yàn)槎嗑€程并發(fā)操作而導(dǎo)致引用計數(shù)錯誤,從而保證了文件對象在所有線程都不再需要時能被正確釋放 。這種原子操作的實(shí)現(xiàn),使得std::shared_ptr在多線程環(huán)境下的引用計數(shù)管理變得高效且可靠,大大降低了因多線程操作導(dǎo)致的內(nèi)存管理錯誤風(fēng)險 。
4.2 訪問對象的線程安全
雖然std::shared_ptr的引用計數(shù)是線程安全的,但對其指向?qū)ο蟮脑L問卻并非如此 。當(dāng)多個線程同時通過std::shared_ptr訪問和修改同一個對象時,如果沒有適當(dāng)?shù)耐酱胧涂赡軙l(fā)數(shù)據(jù)競爭和未定義行為 。例如,在一個多線程的銀行賬戶管理系統(tǒng)中,多個線程可能同時對同一個賬戶對象進(jìn)行取款和存款操作 。如果直接通過std::shared_ptr訪問賬戶對象,而不進(jìn)行任何同步控制,就可能出現(xiàn)數(shù)據(jù)不一致的情況,比如一個線程讀取了賬戶余額后,還未進(jìn)行更新操作,另一個線程又讀取了相同的余額并進(jìn)行操作,最終導(dǎo)致賬戶余額計算錯誤 。
為了保證對std::shared_ptr指向?qū)ο蟮陌踩L問,通常需要使用諸如std::mutex(互斥鎖)、std::lock_guard(RAII 風(fēng)格的鎖管理類)等同步機(jī)制 。下面是一個使用std::mutex來保護(hù)共享對象訪問的代碼示例:
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
class Counter {
public:
Counter() : value(0) {}
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++value;
}
int get() {
std::lock_guard<std::mutex> lock(mtx);
return value;
}
private:
int value;
std::mutex mtx;
};
int main() {
auto counter = std::make_shared<Counter>();
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread([counter]() {
for (int j = 0; j < 100; ++j) {
counter->increment();
}
});
}
for (auto& th : threads) {
th.join();
}
std::cout << "Final counter value: " << counter->get() << std::endl;
return 0;
}在上述代碼中,Counter類包含一個std::mutex成員變量mtx,用于保護(hù)對value成員變量的訪問 。在increment和get成員函數(shù)中,使用std::lock_guard<std::mutex>來自動管理鎖的生命周期,在進(jìn)入函數(shù)時自動加鎖,離開函數(shù)時自動解鎖 。這樣,當(dāng)多個線程同時調(diào)用increment函數(shù)時,通過鎖機(jī)制保證了每次只有一個線程能夠修改value,從而避免了數(shù)據(jù)競爭,確保了線程安全 。
注意事項(xiàng):雖然std::shared_ptr確保了引用計數(shù)的線程安全,但對對象本身的訪問并非線程安全。如果多個線程要修改std::shared_ptr指向的對象,仍然需要額外的同步措施(如使用std::mutex)來保證線程安全。
4.3 多線程修改 std::shared_ptr 指向的對象
如果多個線程需要同時訪問并修改 std::shared_ptr 指向的對象,使用 std::mutex 可以保證線程安全。這里提供一個示例展示如何使用 std::mutex 來保護(hù)對共享對象的訪問和修改。
我們創(chuàng)建一個共享的計數(shù)器對象,多個線程將同時訪問并修改該計數(shù)器。在沒有 std::mutex 保護(hù)的情況下,計數(shù)器的值可能會因數(shù)據(jù)競爭而出現(xiàn)錯誤。通過在訪問和修改計數(shù)器的代碼塊中添加互斥鎖,我們可以確保每個線程按順序訪問該資源,避免數(shù)據(jù)競爭。
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
#include <vector>
class Counter {
public:
int value;
Counter() : value(0) {}
void increment() {
++value;
}
int getValue() const {
return value;
}
};
void thread_func(std::shared_ptr<Counter> counter, std::mutex& mtx) {
for (int i = 0; i < 100; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 加鎖保護(hù)對 counter 的訪問
counter->increment();
}
}
int main() {
auto counter = std::make_shared<Counter>();
std::mutex mtx;
std::vector<std::thread> threads;
// 啟動10個線程,每個線程對 counter 執(zhí)行 100 次 increment 操作
for (int i = 0; i < 10; ++i) {
threads.emplace_back(thread_func, counter, std::ref(mtx));
}
// 等待所有線程完成
for (auto& t : threads) {
t.join();
}
std::cout << "Final counter value: " << counter->getValue() << std::endl; // 期望輸出 1000
return 0;
}在這個例子中,Counter 類的對象由 std::shared_ptr 管理,并在多個線程中共享,在 thread_func 函數(shù)中,每次調(diào)用 counter->increment() 前,都用 std::lock_guard<std::mutex> 鎖定 mtx,保證每次訪問 increment() 是原子操作,std::lock_guard 是 RAII 風(fēng)格的鎖管理器,它會在代碼塊結(jié)束時自動釋放鎖。啟動 10 個線程,每個線程對共享計數(shù)器執(zhí)行 100 次增量操作。通過 std::mutex,我們保證了計數(shù)器的修改是線程安全的。
程序輸出:Final counter value: 1000;在沒有互斥鎖的情況下,counter->increment() 在多個線程中可能會發(fā)生競爭,導(dǎo)致最終計數(shù)值低于預(yù)期的 1000。使用 std::mutex 來保護(hù)對共享資源的訪問,保證了線程安全,確保最終計數(shù)器值為 1000。



































