最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++?內(nèi)存避坑指南之移動語義和智能指針解決"深拷貝"與"內(nèi)存泄漏"的過程

 更新時間:2026年02月09日 08:53:20   作者:念風零壹  
文章介紹了Java和C++中函數(shù)傳參和對象生命周期管理的區(qū)別,C++提供了三種傳參方式:值傳遞、引用傳遞和指針傳遞,C++通過RAII和智能指針(如unique_ptr、shared_ptr和weak_ptr)來管理堆內(nèi)存,避免內(nèi)存泄漏和雙重釋放,感興趣的朋友跟隨小編一起看看吧

1. 函數(shù)傳參

在 Java 中,當我們把一個「對象」傳給函數(shù)時,其實不需要思考太多:傳過去的是引用的拷貝,函數(shù)里修改的對象的內(nèi)容也會反應到外面。

但在 C++ 中情況可能不太一樣,一般來說我們有三個選擇:

1.1. 值傳遞 (Pass-by-Value):默認的「深拷貝」

這是 C++ 和 Java 最大的直覺沖突點。在 C++ 中,如果沒有任何修飾符,編譯器會把整個對象完整地克隆一份。 我們看下面的例子:

#include <vector>
#include <iostream>
// 這里會觸發(fā) std::vector 的拷貝構造函數(shù)
void modify(std::vector<int> v) { 
    v.push_back(999);
    std::cout << "modify內(nèi)vector的長度為: " << v.size() << std::endl;
    // 函數(shù)結束,局部變量 v 被銷毀,999 也隨之消失
    // 外部的 list 毫發(fā)無損
}
int main() {
    // 假設這是一個包含 100 萬個元素的列表
    std::vector<int> bigList(1000000, 1);
    // 調(diào)用時發(fā)生 Deep Copy,性能開銷極大
    modify(bigList); 
    std::cout << "main函數(shù)內(nèi)vector的長度為: " << bigList.size() << std::endl;
    return 0;
}

運行結果為:

modify內(nèi)vector的長度為: 1000001
main函數(shù)內(nèi)vector的長度為: 1000000

對比一下Java代碼:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
    // Java 總是按值傳遞,但對于對象,傳遞的是“引用的值”
    public static void modify(List<Integer> v) {
        v.add(999);
        System.out.println("modify內(nèi)List的長度為: " + v.size());
    }
    public static void main(String[] args) {
        // 創(chuàng)建包含 100 萬個元素的列表
        List<Integer> bigList = new ArrayList<>(Collections.nCopies(1000000, 1));
        // 這里傳遞的是引用,沒有深拷貝,性能開銷極小
        modify(bigList);
        // 注意:這里的長度會變成 1000001
        System.out.println("main中List的長度為: " + bigList.size());
    }
}

運行結果為:

modify內(nèi)List的長度為: 1000001
main中List的長度為: 1000001

由此我們可以得出以下的結論:

  • Java:函數(shù)調(diào)用時傳遞的是引用值。Java 永遠不會隱式地把整個堆上的大對象復制一遍。
  • C++:是“值語義”。函數(shù)里的 vbigList 的完全獨立副本。你在副本上做的任何修改,都不會影響本體。

1.2. C++的引用傳遞 (Pass-by-Reference):&

為了既能修改外部對象,又避免昂貴的拷貝,C++ 提供了 引用(Reference)

在類型后面加一個 &,變量就變成了外部對象的別名(Alias)。我們看下面的代碼:

#include <vector>
#include <iostream>
// 引用傳遞
void modify(std::vector<int>& v) { 
    v.push_back(999);
    // 直接操作內(nèi)存中的同一份數(shù)據(jù)
    std::cout << "modify內(nèi)vector的長度為: " << v.size() << std::endl;
}
int main() {
    std::vector<int> bigList(1000000, 1);
    modify(bigList); 
    std::cout << "main函數(shù)內(nèi)vector的長度為: " << bigList.size() << std::endl;
    return 0;
}

運行結果為:

modify內(nèi)vector的長度為: 1000001
main函數(shù)內(nèi)vector的長度為: 1000001

特點

  1. 零拷貝:無論 List 有多大,這里只傳遞一個綁定的關系(底層通常是指針實現(xiàn))。
  2. 非空保證:引用必須綁定到一個存在的對象上,不存在 null 引用。這比 Java 安全。
  3. 語法透明:在函數(shù)內(nèi)部,你不需要像指針那樣解引用,像操作普通變量一樣操作它即可。

1.3. 指針傳遞 (Pass-by-Pointer):經(jīng)典的“地址”傳遞*

這其實是最接近 Java 底層實現(xiàn)的方式。如果需要傳遞大對象,或者對象可能是空的(nullptr),我們就傳遞它的內(nèi)存地址。

#include <vector>
#include <iostream>
// 指針傳遞
void modify(std::vector<int>* v) { 
    // 判斷是否為空,防止 Crash
    if (v != nullptr) {
        // 語法變化:用 '->' 來訪問成員
        v->push_back(999); 
        std::cout << "modify內(nèi)vector的長度為: " << v->size() << std::endl;
    }
}
int main() {
    std::vector<int> bigList(1000000, 1);
    // 調(diào)用變化:必須顯式取出地址 (&) 傳進去
    modify(&bigList); 
    std::cout << "main函數(shù)內(nèi)vector的長度為: " << bigList.size() << std::endl;
    return 0;
}

運行結果為:

modify內(nèi)vector的長度為: 1000001
main函數(shù)內(nèi)vector的長度為: 1000001

  • 對比 Java:Java 的引用其實就是“受限的指針”。
  • Java: modify(list) 隱式傳遞了地址。
  • C++: modify(&list) 顯式傳遞了地址。
  • 使用場景:通常用于兼容 C 語言接口,或者當參數(shù)是“可選的”(可以傳 nullptr 表示忽略)時。

1.4. 三種方式對比總結

特性值傳遞 (T)引用傳遞 (T&)指針傳遞 (T*)Java (Object)
內(nèi)存行為深拷貝 (Deep Copy)零拷貝 (別名)零拷貝 (傳遞地址)淺拷貝 (復制引用)
修改外部?? 不能? 能? 能? 能
能否為 Null? 不涉及? 不能 (必須綁定對象)? 能 (nullptr)? 能
語法復雜度簡單簡單繁瑣 (*, &, ->)簡單
適用場景int, bool 等小類型首選方案 (非空對象)兼容 C默認行為

2. 對象的生命周期:從手動管理到 RAII 與移動語義

在第一章我們看到:C++ 默認的“值傳遞”會導致性能問題(深拷貝),而“指針傳遞”雖然快,但會導致所有權模糊。

這一章我們深入探討如何既解決安全問題(內(nèi)存泄漏),又解決性能問題(拷貝開銷)。

2.1. 痛點:裸指針帶來的“內(nèi)存泄漏”危機

當我們傳遞一個指針(或者從函數(shù)返回一個指針)時,編譯器只負責傳遞地址。這就帶來了一個靈魂拷問:誰負責 delete 這個對象?

看下面這個看似正常的例子:

#include <iostream>
class Enemy {
public:
    Enemy() { std::cout << "Enemy Created" << std::endl; }
    ~Enemy() { std::cout << "Enemy Destroyed" << std::endl; }
    void attack() { std::cout << "Enemy attacks!" << std::endl; }
};
// 工廠函數(shù):在堆上創(chuàng)建一個對象,并返回指針
Enemy* createEnemy() {
    // 危險的源頭:new 出來的內(nèi)存,必須有人 delete
    return new Enemy(); 
}
void gameLogic() {
    // 獲取指針
    Enemy* boss = createEnemy();
    boss->attack();
    // 假設這里有一段復雜的邏輯
    if (true) {
        std::cout << "Player died, game over early." << std::endl;
        // 致命問題:函數(shù)直接返回了,但 boss 指向的內(nèi)存沒釋放!
        return; 
    }
    // 只有代碼走到這里,內(nèi)存才會被釋放
    delete boss; 
}

后果:只要你在 delete 之前寫了一個 return,或者拋出了一個異常(Exception),這塊內(nèi)存就永遠丟了。Java 程序員可能對此毫無感覺 (JVM有GC機制),但在長時間運行的C++服務器程序(如數(shù)據(jù)庫)中,這會導致內(nèi)存耗盡(OOM)并崩潰。

2.2. 解決方案:GC vs. RAII

為了解決這個問題,Java 和 C++ 是走了兩條完全不同的路。

2.2.1. Java 的做法:垃圾回收 (GC)

Java 認為:程序員不應該操心內(nèi)存釋放,交給虛擬機(JVM)。

  • 機制:JVM 運行后臺線程,定期掃描,發(fā)現(xiàn)沒人引用的對象就回收。
  • 代價不確定性(你不知道它什么時候回收)和 STW (Stop The World)(GC 工作時可能會暫停程序)。

2.2.2. C++ 的做法:RAII (資源獲取即初始化)

C++ 認為:性能和確定性第一。我不要后臺線程,我要利用“棧”的特性來自動管理堆內(nèi)存。

RAII (Resource Acquisition Is Initialization) 的核心原理是將堆內(nèi)存綁定到棧對象上:

  1. 棧對象的鐵律:棧對象(局部變量)一旦離開它的作用域(即大括號 {} 結束),編譯器一定會自動調(diào)用它的析構函數(shù)(Destructor)。無論是因為正常執(zhí)行完、還是中間 return 了、還是拋異常了,必死無疑
  2. RAII 的策略
  • 構造時:在構造函數(shù)里 new 內(nèi)存。
  • 析構時:在析構函數(shù)里 delete 內(nèi)存。

看下面的代碼:我們寫一個包裝類 EnemyWrapper

#include <iostream>
class Enemy {
public:
    Enemy() { std::cout << "Enemy Created" << std::endl; }
    ~Enemy() { std::cout << "Enemy Destroyed" << std::endl; }
    void attack() { std::cout << "Enemy attacks!" << std::endl; }
};
// 工廠函數(shù):在堆上創(chuàng)建一個對象,并返回指針
Enemy* createEnemy() {
    // 危險的源頭:new 出來的內(nèi)存,必須有人 delete
    return new Enemy(); 
}
class EnemyWrapper {
private:
    // 持有原始指針
    Enemy* ptr;
public:
    // 【構造函數(shù)】:獲取資源
    EnemyWrapper() {
        ptr = new Enemy(); 
    }
    // 【析構函數(shù)】:釋放資源 (這是 RAII 的靈魂)
    ~EnemyWrapper() {
        if (ptr != nullptr) {
            delete ptr; // 只要 Wrapper 被銷毀,ptr 指向的內(nèi)存必被釋放
            std::cout << "Wrapper triggered delete!" << std::endl;
        }
    }
    // 模擬指針操作
    void attack() { ptr->attack(); }
};
void gameLogicSafe() {
    // 這是一個棧對象
    EnemyWrapper boss; 
    boss.attack();
    if (true) {
        std::cout << "Game over early." << std::endl;
        // 即使這里 return,棧變量 boss 也會彈出
        return;
        // 編譯器自動插入代碼:call boss.~EnemyWrapper() -> delete ptr
    }
}
int main() {
    gameLogicSafe();
    return 0;
}

運行結果:

Enemy Created
Enemy attacks!
Game over early.
Enemy Destroyed
Wrapper triggered delete!

結論:不管你怎么寫邏輯,內(nèi)存永遠不會泄漏。

2.3. RAII 的新問題

現(xiàn)在 RAII 解決了內(nèi)存泄漏問題。但是,當我們想把這個對象傳遞出去(比如從函數(shù)返回)時,就又有問題了:

2.3.1. 方案 A:直接傳內(nèi)部指針(破壞封裝,回到解放前)

如果把 RAII 對象里的指針拿出來傳遞,那就不再受 RAII 保護了。我們看下面的代碼:

Enemy* getBoss() {
    // 棧對象
    EnemyWrapper wrapper; 
    // 極其危險!
    return wrapper.ptr;   
} // 函數(shù)結束 -> wrapper 析構 -> wrapper.ptr 被 delete
void main() {
    Enemy* p = getBoss(); 
    // 崩潰!p 指向的內(nèi)存已經(jīng)被 wrapper 刪掉了(懸空指針)
    p->attack(); 
}

結論:絕對不能把 RAII 管理的裸指針泄露出去,否則 RAII 就白做了。

2.3.2. 方案 B:拷貝 RAII 對象(安全但極慢)

既然不能傳裸指針,那我們只能傳 EnemyWrapper 這個對象本身。在 C++11 之前,這意味著深拷貝。我們看下面的代碼:

#include <iostream>
// 模擬一個“昂貴”的資源
class Enemy {
public:
    Enemy() { std::cout << "  [堆資源] Enemy 被 new 出來了 (耗時操作...)" << std::endl; }
    ~Enemy() { std::cout << "  [堆資源] Enemy 被 delete 掉了" << std::endl; }
};
// RAII 包裝類
class EnemyWrapper {
private:
    Enemy* ptr;
public:
    // 【構造函數(shù)】:獲取資源
    EnemyWrapper() {
        std::cout << "[Wrapper] 普通構造" << std::endl;
        ptr = new Enemy(); 
    }
    // 【析構函數(shù)】:釋放資源
    ~EnemyWrapper() {
        if (ptr != nullptr) {
            delete ptr;
            std::cout << "[Wrapper] 析構,釋放資源" << std::endl;
        }
    }
    // ==========================================
    // 【拷貝構造函數(shù)】(Deep Copy) -> 性能瓶頸在這里!
    // ==========================================
    // 當我們需要復制這個對象時(比如函數(shù)返回),必須調(diào)用這個函數(shù)
    EnemyWrapper(const EnemyWrapper& other) {
        std::cout << "[Wrapper] ?? 觸發(fā)深拷貝!必須分配新內(nèi)存..." << std::endl;
        // 笨重的深拷貝:
        // A. 必須 new 一個新的 Enemy (不能共用指針,否則會 double free)
        ptr = new Enemy(); 
        // B. (如果有數(shù)據(jù)) 還要把 other.ptr 里的數(shù)據(jù)復制過來
        // *ptr = *(other.ptr); 
    }
};
// 觸發(fā)拷貝的函數(shù)
EnemyWrapper createBoss() {
    std::cout << "--- 進入函數(shù) ---" << std::endl;
    // Step 1: temp 創(chuàng)建,new Enemy (地址 A)
    EnemyWrapper temp; 
    std::cout << "--- 準備返回 ---" << std::endl;
    // Step 2: return 時,因為要傳值給外面,必須【拷貝】temp
    // 這意味著:調(diào)用拷貝構造函數(shù) -> new Enemy (地址 B) -> 復制數(shù)據(jù)
    return temp; 
    // Step 3: 函數(shù)結束,temp 離開作用域,delete A
    // (結果:我們?yōu)榱说玫?B,申請了 A,復制給 B,然后刪了 A。A 只是個中間商。)
}
int main() {
    std::cout << "=== 演示開始 ===" << std::endl;
    EnemyWrapper boss = createBoss();
    std::cout << "=== 演示結束 ===" << std::endl;
    return 0;
}

注意,運行上面的代碼需要關閉RVO(返回值優(yōu)化),需要在編譯命令上加-fno-elide-constructors參數(shù),例如:g++ example.cpp -fno-elide-constructors -o example。

所謂的RVO正現(xiàn)代 C++ 編譯器最“聰明”的地方之一,本來按照 C++ 的語法規(guī)則:

  1. createBoss 里創(chuàng)建 temp。
  2. return 時,應該把 temp 拷貝main 里的 boss。
  3. 銷毀 temp

但是編譯器覺得這樣太蠢了,所以它“作弊”了:
它根本沒有在 createBoss 里創(chuàng)建 temp,而是直接在 main 函數(shù)里 boss 的內(nèi)存地址上執(zhí)行了構造函數(shù)。
結果就是:0 次拷貝,0 次移動,直接構造。

雖然編譯器能優(yōu)化 return,但也存很多在編譯器無法優(yōu)化的場景(比如 vector.push_back 或者復雜的賦值)。

上述例子禁用優(yōu)化之后的運行結果為:

=== 演示開始 ===
--- 進入函數(shù) ---
[Wrapper] 普通構造
  [堆資源] Enemy 被 new 出來了 (耗時操作...)
--- 準備返回 ---
[Wrapper] ?? 觸發(fā)深拷貝!必須分配新內(nèi)存...
  [堆資源] Enemy 被 new 出來了 (耗時操作...)
  [堆資源] Enemy 被 delete 掉了
[Wrapper] 析構,釋放資源
=== 演示結束 ===
  [堆資源] Enemy 被 delete 掉了
[Wrapper] 析構,釋放資源

這里的痛點
我們陷入了死循環(huán):

  • ?用指針 -> 不安全(內(nèi)存泄漏或懸空指針)。
  • 安全?用 RAII -> (必須深拷貝,因為不能讓兩個 RAII 對象同時擁有同一個指針,否則會 double free)。

我們需要一種機制:既能保留 RAII 的殼子(安全),又能像指針一樣只傳遞地址(快)。

2.4. 什么是右值 (Rvalue)?

為了打破這個僵局,C++11 引入了 右值引用 (&&)。但首先,我們要搞清楚什么是“右值”。

作為開發(fā)者,不需要背誦復雜的定義,只需要掌握一個黃金法則

對它取地址 (&) 的,就是左值 (Lvalue)。
不能對它取地址的,就是右值 (Rvalue)。

2.4.1. 誰是左值?誰是右值?

我們通過幾行簡單的代碼來分辨:

int a = 10; 
  • a 是左值:
  • 為什么? 因為你可以寫 &a,能拿到它的內(nèi)存地址。它在棧上有一個固定的家。
  • 生命周期:持久,直到大括號 } 結束。
  • 10 是右值:
  • 為什么? 它是字面量。你試著寫 int* p = &10;,編譯器會直接報錯。它沒有地址,它只是代碼里的一個數(shù)字。

2.4.2. 隱藏的右值(臨時對象)

對于對象來說,右值往往是一個“無名無姓的幽靈對象”。這是最容易被忽視的場景。

EnemyWrapper getBoss() {
    // 返回一個新創(chuàng)建的對象
    return EnemyWrapper();
}
void main() {
    EnemyWrapper boss = getBoss(); 
}

問題:getBoss() 執(zhí)行完的那一瞬間,發(fā)生了什么?

  1. 函數(shù)內(nèi)部創(chuàng)建了一個 EnemyWrapper 對象。
  2. 函數(shù)返回時,這個對象被扔了出來。
  3. 在它被賦值給變量 boss 之前,它漂浮在虛空中。

這個漂浮在虛空中的對象,就是 右值。

  • 特征:它存在,占用了內(nèi)存,但沒有名字。
  • 命運它馬上就要死了。一旦賦值語句結束,這個臨時對象就會析構。

2.4.3.std::move()到底做了什么?

你經(jīng)常會看到 std::move(x)。很多人誤以為它會移動數(shù)據(jù),其實它什么都沒移動。它的作用只有一個:身份欺詐。

// a 是左值,活得好好的
EnemyWrapper a; 
// 強行把 a 標記為右值
EnemyWrapper b = std::move(a); 
  • a 本來是左值。
  • std::move(a) 相當于給 a 貼了個條子:“這輛車我不想要了,當廢品處理”。
  • 于是,a 被強制轉(zhuǎn)換成了 右值。
  • b 看到這個條子,就會認為 a 是個將死之物,從而直接“偷走”它的資源。

2.5. 終極方案:移動語義 (Move Semantics)

既然我們能識別出右值(將死之物),我們就可以利用這一點來優(yōu)化 RAII。

我們在 EnemyWrapper 里加一個特殊的構造函數(shù)——移動構造函數(shù)。它專門接收右值引用 (&&)。

移動的本質(zhì)就是:合法的竊取。

#include <iostream>
// 模擬一個“昂貴”的資源
class Enemy {
public:
    Enemy() { std::cout << "  [堆資源] Enemy 被 new 出來了 (耗時操作...)" << std::endl; }
    ~Enemy() { std::cout << "  [堆資源] Enemy 被 delete 掉了" << std::endl; }
};
// RAII 包裝類
class EnemyWrapper {
private:
    Enemy* ptr;
public:
    // 【構造函數(shù)】:獲取資源
    EnemyWrapper() {
        std::cout << "[Wrapper] 普通構造" << std::endl;
        ptr = new Enemy(); 
    }
    // 【析構函數(shù)】:釋放資源
    ~EnemyWrapper() {
        if (ptr != nullptr) {
            delete ptr;
            std::cout << "[Wrapper] 析構,釋放資源" << std::endl;
        }
    }
    // ==========================================
    // 【拷貝構造函數(shù)】(Deep Copy) -> 性能瓶頸在這里!
    // ==========================================
    // 當我們需要復制這個對象時(比如函數(shù)返回),必須調(diào)用這個函數(shù)
    EnemyWrapper(const EnemyWrapper& other) {
        std::cout << "[Wrapper] ?? 觸發(fā)深拷貝!必須分配新內(nèi)存..." << std::endl;
        // 笨重的深拷貝:
        // A. 必須 new 一個新的 Enemy (不能共用指針,否則會 double free)
        ptr = new Enemy(); 
        // B. (如果有數(shù)據(jù)) 還要把 other.ptr 里的數(shù)據(jù)復制過來
        // *ptr = *(other.ptr); 
    }
    // 【移動構造】(Move) - C++11 的新方案
    // 參數(shù)是 &&,表示對方是“將死之物”
    EnemyWrapper(EnemyWrapper&& other) noexcept {
        // 1. 偷梁換柱:把對方的指針拿過來
        this->ptr = other.ptr;
        // 2. 毀滅證據(jù):把對方的指針設為 nullptr
        // 這一步至關重要!
        // 當 other 析構時,它會 delete nullptr (什么也不做)
        // 從而避免了資源被誤刪
        other.ptr = nullptr; 
        std::cout << "Move: Ownership transferred!" << std::endl;
    }
};
// 觸發(fā)移動構造函數(shù)
EnemyWrapper createBoss() {
    // 這一行代碼做了兩件事:
    // 1. 在【?!可戏峙淞?EnemyWrapper 這個殼子的內(nèi)存(非???,不需要 new)
    // 2. 自動調(diào)用了它的構造函數(shù)
    EnemyWrapper temp; 
    // temp 是局部變量,返回時被視為右值
    return temp; 
}
int main() {
    // 1. createBoss 返回臨時對象(右值)
    // 2. 觸發(fā)【移動構造函數(shù)】
    // 3. main 里的 boss 直接接管了 temp 里的指針
    // 4. temp 變成空殼被銷毀
    EnemyWrapper boss = createBoss(); 
    // 結果:
    // - 沒有發(fā)生 Deep Copy (省了 new/copy)
    // - 沒有傳遞裸指針 (全程都在 RAII 包裝下,非常安全)
}

運行結果:

[Wrapper] 普通構造
  [堆資源] Enemy 被 new 出來了 (耗時操作...)
Move: Ownership transferred!
  [堆資源] Enemy 被 delete 掉了
[Wrapper] 析構,釋放資源

可以看到,現(xiàn)在沒有深拷貝操作了。但看到這里,可能大家還有有幾個問題:

2.5.1 問題 1:為什么不能在拷貝構造函數(shù)中“掠奪”資源?

你可能會想:“能不能別搞什么移動構造函數(shù)了,直接改寫拷貝構造函數(shù),把 const 去掉,然后在里面偷指針?”

答案是:語法上行得通,但在邏輯上是“災難”。

2.5.1.1. 理由 A:契約精神 (語義混淆)

在編程世界里,“拷貝 (Copy)”這個詞是有明確定義的:制作副本,原件不受影響。

如果我寫 b = a;,按照人類的直覺,a 應該還在那里,完好無損。
如果你在拷貝函數(shù)里搞“掠奪”,就會出現(xiàn)這種恐怖場景:

// 假設這是“魔改版”的拷貝構造函數(shù) (沒有 const)
EnemyWrapper(EnemyWrapper& other) {
    this->ptr = other.ptr;
    other.ptr = nullptr; // 偷偷把原件毀了!
}
void logicalDisaster() {
    EnemyWrapper a; // a 有資源
    // 我只想做一個備份
    EnemyWrapper b = a; 
    // 災難發(fā)生:a 變成空殼了!
    // 后面的代碼如果繼續(xù)用 a,程序直接崩潰。
    a.attack(); // Crash!
}

結論:如果拷貝會破壞原件,那就不能叫“拷貝”,那叫“搶劫”。程序員無法通過代碼一眼看出 b = a 到底安全不安全。為了區(qū)分“復制”和“轉(zhuǎn)移”,我們需要兩個不同的函數(shù)。

2.5.1.2. 理由 B:語法限制 (Const Correctness)

標準的拷貝構造函數(shù)簽名是 const EnemyWrapper& other

  • 那個 const 是鐵律。它向調(diào)用者保證:“你放心傳給我,我絕不動你的一根毫毛”。
  • 因為有 const,編譯器禁止你寫 other.ptr = nullptr;。
  • 如果你強行去掉 const,它就無法接受臨時對象(因為臨時對象通常綁定到 const 引用),導致通用性大打折扣。

2.5.2 問題 2:編譯器是如何區(qū)分調(diào)用“拷貝”還是“移動”的?

這是一個非常精彩的“函數(shù)重載決議” (Overload Resolution) 過程。

編譯器并不是通過“猜”你的意圖來決定的,它是通過參數(shù)類型匹配來決定的。

2.5.2.1. 兩個函數(shù)的簽名對比
  • 拷貝構造EnemyWrapper(const EnemyWrapper&) -> 接收 左值 (和右值,作為備胎)。
  • 移動構造EnemyWrapper(EnemyWrapper&&) -> 專門接收 右值。
2.5.2.2.createBoss里的決策過程

當你在 return temp; 時(假設 RVO 被禁用,必須發(fā)生傳遞):

  1. 判定 temp 的狀態(tài):
    雖然 temp 在函數(shù)里定義時是個左值,但因為它馬上要被 return 了,即將銷毀,C++ 編譯器會自動把它視為 xvalue (將亡值),也就是一種右值。
  2. 開始匹配構造函數(shù)
    編譯器看著 main 函數(shù)里正在等待接收的 boss 對象,問:“我手里有一個右值,我該調(diào)用哪個構造函數(shù)來初始化 boss?”
  • 選手 A (拷貝):我要 const &。可以接收右值嗎?可以(const 引用能接萬物),但只是“兼容”。
  • 選手 B (移動):我要 &&??梢越邮沼抑祮??完美匹配!
  1. 擇優(yōu)錄取
    編譯器發(fā)現(xiàn)選手 B 是精確匹配 (Exact Match),所以毫不猶豫地選擇了移動構造函數(shù)。
2.5.2.3. 只有拷貝構造函數(shù)時會怎樣?

如果你沒寫移動構造函數(shù)(C++98 的情況):

  • 編譯器手里拿著右值,發(fā)現(xiàn)沒有 && 的構造函數(shù)。
  • 它會退而求其次,發(fā)現(xiàn) const &(拷貝構造)也能接收右值。
  • 于是含淚調(diào)用了拷貝構造函數(shù)(深拷貝)。

2.5.3. 總結

場景傳遞給構造函數(shù)的參數(shù)優(yōu)先匹配備選匹配結果
EnemyWrapper b = a;左值 (a 還要接著用)(const T&) 拷貝深拷貝
return temp;右值 (temp 馬上死)(T&&) 移動(const T&) 拷貝移動 (偷)
b = std::move(a);右值 (強轉(zhuǎn)的)(T&&) 移動(const T&) 拷貝移動 (偷)

一句話總結:編譯器看“參數(shù)類型”。如果是“將死之物(右值)”,優(yōu)先匹配 && 版(移動);如果是“普通對象(左值)”,只能匹配 const & 版(拷貝)。

2.6. 避坑指南:return時千萬別用move

那在 createBoss 函數(shù)里,需要寫 return std::move(temp); 嗎?

答案是:不要!

EnemyWrapper createBoss() {
    EnemyWrapper temp; 
    // 正確寫法:編譯器會自動優(yōu)化 (RVO)
    // 編譯器會直接在外部變量的內(nèi)存地址上構造 temp,連“移動”都不需要做!
    // 成本 = 0
    return temp; 
    // 錯誤寫法:畫蛇添足
    // return std::move(temp); 
    // 這會強行打斷編譯器的 RVO 優(yōu)化,強制執(zhí)行一次“移動構造”。
    // 成本 > 0 (雖然也很低,但是屬于“負優(yōu)化”)
}

std::move 到底用在哪里?
用在你需要顯式轉(zhuǎn)移一個左值的所有權時:

int main() {
    // 1. RVO 自動優(yōu)化,這里沒有拷貝,也沒有移動
    EnemyWrapper boss1 = createBoss(); 
    // 2. 假設你想把 boss1 轉(zhuǎn)給 boss2
    // EnemyWrapper boss2 = boss1; // 編譯報錯(假設禁用了拷貝)或深拷貝(慢)
    // 3. 這里必須用 std::move!
    // 因為 boss1 是個活著的左值,編譯器不敢自動動它。
    // 你必須手動簽署“放棄所有權書”。
    EnemyWrapper boss2 = std::move(boss1); 
    // 此刻:boss2 拿到了指針,boss1 變成了空殼。
}

2.7. 移動語義的本質(zhì):所有權轉(zhuǎn)移 (Ownership Transfer)

很多從 Java/Python 轉(zhuǎn)過來的開發(fā)者,在理解“移動”時容易陷入誤區(qū),認為數(shù)據(jù)真的在內(nèi)存里“搬家”了。

移動語義的本質(zhì),并不是移動數(shù)據(jù),而是“所有權的交接”。

2.7.1. 核心思想:唯一責任制 (Sole Ownership)

在 Java 中,對象的所有權是共享的(Shared)。

  • 你有一個 List,傳給函數(shù) A,傳給函數(shù) B,大家都拿著引用的副本。
  • 誰負責銷毀它?誰都不負責。GC 負責。
  • 這種模式很省心,但在資源敏感(如文件句柄、網(wǎng)絡連接、互斥鎖)或高性能場景下,會導致資源釋放的不可控。

在現(xiàn)代 C++(RAII + Move)中,我們強調(diào)獨占所有權(Exclusive Ownership)。

  • 原則:對于某一塊堆內(nèi)存資源,在任何時刻,只能有一個對象對它負責。
  • 推論:既然只有一個主人,那么當這個主人被銷毀時,資源必須被銷毀。

2.7.2. 移動的物理動作:淺拷貝 + 抹除原主 (Shallow Copy + Nullify)

既然資源只能有一個主人,那么當我們需要把資源傳給別人時,就不能是“分享”(Copy),只能是“過戶”(Move)。

移動語義在匯編層面的本質(zhì)只有兩步:

  1. 竊取指針(Shallow Copy)
  • 新主人(dest)把舊主人(src)手里的指針值(地址)復制過來。
  • 此刻,兩個人都指向了同一個資源(危險狀態(tài)?。?。
  1. 抹除舊主(Nullify)
  • 最關鍵的一步:把舊主人(src)手里的指針設為 nullptr。
  • 結果,舊主人失去了對資源的控制權,變成了空殼。

2.7.3. 現(xiàn)實世界的類比

為了理解“拷貝”和“移動”的區(qū)別,我們可以用 “房產(chǎn)證” 做比喻:

  • 資源(Resource):房子(不動產(chǎn),很貴,搬不動)。
  • 指針(Pointer):房產(chǎn)證(一張紙,很輕)。

場景 A:深拷貝 (Deep Copy) —— C++98 的做法

  • 操作:你想把房子給你的兒子。
  • C++98:你必須在隔壁蓋一棟一模一樣的新房子(new),然后把新房子的房產(chǎn)證給兒子。
  • 代價:極度浪費錢和時間。

場景 B:移動語義 (Move Semantics) —— C++11 的做法

  • 操作:你想把房子給你的兒子。
  • C++11:你把手里的房產(chǎn)證直接交給兒子,然后把你自己的名字從房管局注銷。
  • 代價:房子根本沒動,只是持有人變了。

2.7.4. 為什么說這是“所有權”的體現(xiàn)?

回到我們之前的 EnemyWrapper 代碼:

EnemyWrapper(EnemyWrapper&& other) noexcept {
    // 1. 接過房產(chǎn)證
    this->ptr = other.ptr; 
    // 2. 原主注銷,從此這房子和你無關了
    other.ptr = nullptr; 
}

這里體現(xiàn)了 C++ 最硬核的契約精神:

"我移動了你,你就不再擁有它。后續(xù)的清理工作由我負責,你只需安靜地離開。"

這解決了 C++ 長期以來的“雙重釋放” (Double Free) 問題:因為原主變成了 nullptr,它的析構函數(shù) delete nullptr 不會產(chǎn)生任何副作用。

2.8. 總結

  1. 裸指針:雖快,但無法保證內(nèi)存一定會釋放(容易泄漏)。
  2. RAII:通過包裝類保證了內(nèi)存一定釋放,但在 C++98 中,為了保證安全(防止多次釋放),傳遞對象時必須進行深拷貝,導致性能低下。
  3. 右值 (Rvalue):指那些沒有名字、即將銷毀的臨時對象(不能取地址)。
  4. 移動語義 (Move):是完美的折中方案。它允許 RAII 對象在“交接班”時,通過識別右值,直接把內(nèi)部的指針所有權轉(zhuǎn)移給對方,既保留了 RAII 的外殼(安全),又只傳遞了指針(高效)。

3. 智能指針與 Java GC

在前兩章節(jié)中,我們已經(jīng)掌握了 RAII(利用棧管理堆)移動語義(所有權轉(zhuǎn)移)。如果仔細觀察,會發(fā)現(xiàn)我們手寫的 EnemyWrapper 其實就是一個簡陋的“智能指針”。

C++ 標準庫把這種模式標準化了,提供了三個現(xiàn)成的工具,統(tǒng)稱為 智能指針 (Smart Pointers)。它們徹底終結了手動寫 delete 的歷史。

3.1. 什么是智能指針?

智能指針不是指針,它是一個 C++ 類(Class)。

  • 它在棧上(像個普通變量)。
  • 它里面藏著一個裸指針(指向堆)。
  • 它利用 RAII,在析構函數(shù)里自動 delete 那個裸指針。
  • 它重載了 *-> 運算符,讓你用起來感覺像個指針。

C++ 提供了三種智能指針,分別對應三種所有權模式

  1. std::unique_ptr:你是我的唯一(獨占所有權)。
  2. std::shared_ptr:我們共享它(共享所有權)。
  3. std::weak_ptr:我就靜靜地看著你(弱引用,不增加計數(shù))。

3.2.std::unique_ptr(獨占)

這是 C++ 中最推薦、最常用的智能指針。90% 的場景都應該用它。

3.2.1. 核心特性

  • 獨占性:同一時間,只能有一個 unique_ptr 指向那個對象。
  • 不可拷貝:你不能復制它(否則會有兩個主人,這就是我們之前手動禁用的拷貝構造)。
  • 可移動:你可以把所有權移交給別人(利用移動語義)。
  • 零開銷:它的性能和裸指針完全一樣。它只是多了一層編譯期的檢查,運行時沒有任何額外負擔。

3.2.2. 代碼示例

#include <iostream>
#include <memory> // 必須包含這個頭文件
// 模擬一個“昂貴”的資源
class Enemy {
public:
    Enemy() { std::cout << "  [堆資源] Enemy 被 new 出來了 (耗時操作...)" << std::endl; }
    ~Enemy() { std::cout << "  [堆資源] Enemy 被 delete 掉了" << std::endl; }
    void attack() { std::cout << "Enemy attacks!" << std::endl; }
};
void uniqueDemo() {
    // 1. 創(chuàng)建 (推薦用 make_unique,不要直接 new)
    std::unique_ptr<Enemy> boss = std::make_unique<Enemy>(); 
    boss->attack(); // 用起來像指針
    // 2. 禁止拷貝!
    // std::unique_ptr<Enemy> boss2 = boss; // ? 編譯報錯!
    // 3. 可以移動!
    // 這里的 move 就像我們在 Part 2 學的那樣,把所有權轉(zhuǎn)給 p2
    std::unique_ptr<Enemy> boss2 = std::move(boss); 
    // 此時:
    // boss 變成了 nullptr (空)
    // boss2 擁有了對象
} // 函數(shù)結束 -> boss2 析構 -> 自動 delete Enemy
int main() {
    uniqueDemo();
    return 0;
}

運行結果如下:

  [堆資源] Enemy 被 new 出來了 (耗時操作...)
Enemy attacks!
  [堆資源] Enemy 被 delete 掉了

3.3.std::shared_ptr(共享)

這貨看起來最像 Java 的引用。它允許多個指針指向同一個對象。

3.3.1. 核心特性

  • 引用計數(shù) (Reference Counting):它內(nèi)部維護一個計數(shù)器。
  • 每多一個人指向它,計數(shù) +1。
  • 每有一個人銷毀或不再指向它,計數(shù) -1。
  • 當計數(shù)變成 0 時,自動 delete 對象。
  • 有開銷:為了維護這個計數(shù)器(而且要保證多線程安全),它比 unique_ptr 慢一點點,內(nèi)存也多一點(因為要存計數(shù)器)。

3.3.2. 代碼示例

#include <iostream>
#include <memory> // 必須包含這個頭文件
// 模擬一個“昂貴”的資源
class Enemy {
public:
    Enemy() { std::cout << "  [堆資源] Enemy 被 new 出來了 (耗時操作...)" << std::endl; }
    ~Enemy() { std::cout << "  [堆資源] Enemy 被 delete 掉了" << std::endl; }
    void attack() { std::cout << "Enemy attacks!" << std::endl; }
};
void sharedDemo() {
    // 1. 創(chuàng)建 (引用計數(shù) = 1)
    std::shared_ptr<Enemy> p1 = std::make_shared<Enemy>();
    {
        // 2. 拷貝 (引用計數(shù) = 2)
        // 注意:這里是可以直接 "=" 賦值的,因為它是共享的
        std::shared_ptr<Enemy> p2 = p1; 
        p2->attack();
        std::cout << "當前引用數(shù): " << p1.use_count() << std::endl; // 輸出 2
    } 
    // p2 離開作用域,引用計數(shù) -1 (變回 1)。對象還活著!
    p1->attack(); 
} // 函數(shù)結束,p1 離開,引用計數(shù) -1 (變成 0) -> delete Enemy
int main() {
    sharedDemo();
    return 0;
}

運行結果如下:

  [堆資源] Enemy 被 new 出來了 (耗時操作...)
Enemy attacks!
當前引用數(shù): 2
Enemy attacks!
  [堆資源] Enemy 被 delete 掉了

3.4. C++ shared_ptr vs Java GC

這是面試和架構設計中的核心考點。C++ 的 shared_ptr 和 Java 的引用看起來很像,但底層邏輯完全不同。

3.4.1. 機制對比:引用計數(shù) vs 可達性分析

特性C++ (shared_ptr)Java (Garbage Collection)
核心算法引用計數(shù) (Reference Counting)可達性分析 (Tracing / Reachability)
判定死亡只要計數(shù)器歸零,立刻死亡。從 GC Roots (如棧變量) 出發(fā),不到的對象才算死。
釋放時機確定性 (Deterministic)。最后一個指針銷毀的那一瞬間,對象必死。不確定性???GC 心情,可能幾秒后,可能內(nèi)存不夠時。
性能開銷平攤。每次賦值都有微小的原子操作開銷。集中。平時很快,但 GC 運行時可能導致 "Stop The World" (卡頓)。
循環(huán)引用無法處理。A 指向 B,B 指向 A,兩人計數(shù)都是 1,永遠不歸零 -> 內(nèi)存泄漏。完美處理。GC 發(fā)現(xiàn)這倆貨雖然互相指,但外面沒人指它們,直接一鍋端。

3.4.2. 場景演示:循環(huán)引用 (C++ 的阿喀琉斯之踵)

這是 C++ shared_ptr 最大的坑。

#include <iostream>
#include <memory>
// 前置聲明:因為 A 里面要用 B,B 里面要用 A,必須先告訴編譯器 B 是個類
class B; 
class A {
public:
    // A 持有 B 的強引用 (shared_ptr)
    std::shared_ptr<B> ptrB; 
    A() { std::cout << "A Created (構造)" << std::endl; }
    ~A() { std::cout << "A Destroyed (析構) <--- 如果看到這句話,說明沒泄露" << std::endl; }
};
class B {
public:
    // B 持有 A 的強引用 (shared_ptr) -> 導致死鎖
    std::shared_ptr<A> ptrA; 
    B() { std::cout << "B Created (構造)" << std::endl; }
    ~B() { std::cout << "B Destroyed (析構) <--- 如果看到這句話,說明沒泄露" << std::endl; }
};
int main() {
    std::cout << "=== 進入作用域 ===" << std::endl;
    {
        // 1. 創(chuàng)建對象
        // 此時 A 的計數(shù) = 1 (只有變量 a 指向它)
        // 此時 B 的計數(shù) = 1 (只有變量 b 指向它)
        std::shared_ptr<A> a = std::make_shared<A>();
        std::shared_ptr<B> b = std::make_shared<B>();
        std::cout << "1. 初始引用計數(shù):" << std::endl;
        std::cout << "   A counts: " << a.use_count() << std::endl;
        std::cout << "   B counts: " << b.use_count() << std::endl;
        // 2. 建立循環(huán)引用 (互相鎖死)
        std::cout << "2. 建立循環(huán)引用 (a->ptrB = b; b->ptrA = a;)" << std::endl;
        a->ptrB = b; // B 的計數(shù) +1 -> 變成 2 (b 變量 + a.ptrB)
        b->ptrA = a; // A 的計數(shù) +1 -> 變成 2 (a 變量 + b.ptrA)
        std::cout << "   A counts: " << a.use_count() << std::endl;
        std::cout << "   B counts: " << b.use_count() << std::endl;
        std::cout << "--- 準備離開作用域 ---" << std::endl;
    } // 3. 這里!離開作用域!
    // 正常邏輯:
    // - 棧變量 a 銷毀 -> A 計數(shù)減 1 (2 -> 1) -> 不為 0,A 不死!
    // - 棧變量 b 銷毀 -> B 計數(shù)減 1 (2 -> 1) -> 不為 0,B 不死!
    // 結果:A 拿著 B,B 拿著 A,誰也撒不開手。堆內(nèi)存永遠無法釋放。
    std::cout << "=== 離開作用域 (main 結束) ===" << std::endl;
    std::cout << "警告:你沒有看到析構函數(shù)的日志,說明發(fā)生了內(nèi)存泄漏!" << std::endl;
    return 0;
}

運行結果如下:

=== 進入作用域 ===
A Created (構造)
B Created (構造)
1. 初始引用計數(shù):
   A counts: 1
   B counts: 1
2. 建立循環(huán)引用 (a->ptrB = b; b->ptrA = a;)
   A counts: 2
   B counts: 2
--- 準備離開作用域 ---
=== 離開作用域 (main 結束) ===
警告:你沒有看到析構函數(shù)的日志,說明發(fā)生了內(nèi)存泄漏!

而Java 對此表示毫無壓力:Java GC 由于有GC Root,會發(fā)現(xiàn) A 和 B 這一坨東西和外界斷開了聯(lián)系,直接把它倆都回收了。

3.5.std::weak_ptr(打破循環(huán)的救星)

為了解決上面的循環(huán)引用問題,C++ 引入了 weak_ptr

  • 弱引用:它指向 shared_ptr 管理的對象,但是不增加引用計數(shù)
  • 旁觀者:它只是看著對象,不能直接用。如果要用,必須先“升級”為 shared_ptr(并通過升級結果判斷對象是否已經(jīng)死了)。

修復上面的代碼:

我們只需要把 B 里面的指針改成 weak_ptr

#include <iostream>
#include <memory>
class B; // 前置聲明
class A {
public:
    // A 持有 B 的【強引用】(shared_ptr)
    // 意味著:只要 A 活著,B 就不能死
    std::shared_ptr<B> ptrB; 
    A() { std::cout << "A Created (構造)" << std::endl; }
    ~A() { std::cout << "A Destroyed (析構)" << std::endl; }
};
class B {
public:
    // 關鍵修改:B 持有 A 的【弱引用】(weak_ptr)
    // 意味著:B 只是看著 A,但 B 不決定 A 的生死。
    // weak_ptr 不會增加 shared_ptr 的引用計數(shù)!
    std::weak_ptr<A> ptrA; 
    B() { std::cout << "B Created (構造)" << std::endl; }
    ~B() { std::cout << "B Destroyed (析構)" << std::endl; }
};
int main() {
    std::cout << "=== 進入作用域 ===" << std::endl;
    {
        // 1. 創(chuàng)建對象
        std::shared_ptr<A> a = std::make_shared<A>();
        std::shared_ptr<B> b = std::make_shared<B>();
        // 2. 建立引用
        std::cout << "--- 建立連接 ---" << std::endl;
        a->ptrB = b; // A 強引用 B。B 的計數(shù) = 2 (main里的b + A里的ptrB)
        b->ptrA = a; // B 弱引用 A。A 的計數(shù) = 1 (只有main里的a) !!!
        std::cout << "當前引用計數(shù) (關鍵點):" << std::endl;
        // A 的計數(shù)只有 1,因為 weak_ptr 不算數(shù)
        std::cout << "   A counts: " << a.use_count() << " (只有 main 持有它)" << std::endl; 
        // B 的計數(shù)是 2,因為 A 強引用著它
        std::cout << "   B counts: " << b.use_count() << " (main 和 A 都持有它)" << std::endl;
        std::cout << "--- 準備離開作用域 ---" << std::endl;
    } 
    // 3. 離開作用域的過程:
    // Step 1: 變量 'a' 銷毀。
    //    A 的引用計數(shù)從 1 變成 0。
    //    -> A 死了!打印 "A Destroyed"。
    //    -> A 析構時,會自動銷毀它的成員 ptrB。
    // Step 2: A 的成員 ptrB 被銷毀。
    //    B 的引用計數(shù)從 2 減為 1。
    // Step 3: 變量 'b' 銷毀。
    //    B 的引用計數(shù)從 1 變成 0。
    //    -> B 死了!打印 "B Destroyed"。
    std::cout << "=== 離開作用域 (main 結束) ===" << std::endl;
    return 0;
}

運行結果如下(可以看到清晰的析構日志,證明沒有內(nèi)存泄漏:):

=== 進入作用域 ===
A Created (構造)
B Created (構造)
--- 建立連接 ---
當前引用計數(shù) (關鍵點):
   A counts: 1 (只有 main 持有它)
   B counts: 2 (main 和 A 都持有它)
--- 準備離開作用域 ---
A Destroyed (析構)
B Destroyed (析構)
=== 離開作用域 (main 結束) ===

3.6. 總結與最佳實踐

3.6.1. 對比總結

  1. C++ RAII / 智能指針
  • 優(yōu)點即時釋放(不用等 GC),資源利用率極高,無 STW 卡頓。非常適合做實時系統(tǒng)、游戲引擎、高頻交易。
  • 缺點:有思維負擔,需要手動處理循環(huán)引用(weak_ptr)。
  1. Java GC
  • 優(yōu)點開發(fā)效率高,不用關心循環(huán)引用,只要不瞎搞很難內(nèi)存泄漏。
  • 缺點:釋放時機不可控,GC 運行時有性能波動,內(nèi)存占用通常比 C++ 高。

關于開銷的真相
很多人認為 C++ 一定比 Java 快,但在內(nèi)存分配上,Java 其實往往更快。Java 的 new 只是指針后移(Pointer Bump),極其廉價;而 C++ 的 malloc/new 需要去空閑鏈表中尋找合適的內(nèi)存塊。
C++ 的優(yōu)勢在于運行時期的平穩(wěn):它沒有 GC 那個不定時觸發(fā)的“大掃除”,因此非常適合對延遲 (Latency) 極度敏感的場景(如高頻交易、游戲引擎、實時控制系統(tǒng)),而 Java 更適合追求吞吐量 (Throughput) 的后端服務。

3.6.2. C++ 避坑指南

  1. **默認首選 std::unique_ptr**。除非你真的需要多個人共享所有權,否則別用 shared_ptr
  2. **絕不使用 new**。
  • std::make_unique<T>() 代替 new T()。
  • std::make_shared<T>() 代替 new T()。
  • 這不僅代碼短,而且能防止某些極端情況下的內(nèi)存泄漏。
  1. 遇到循環(huán)引用,立刻想到把其中一邊換成 std::weak_ptr。

現(xiàn)在,我們已經(jīng)掌握了 C++ 內(nèi)存管理的核心:對象默認在棧上,堆對象用 unique_ptr 管,共享對象用 shared_ptr 管,循環(huán)引用用 weak_ptr 破。

4. 結語

從 Java 的“全自動駕駛”切換到 C++ 的“手動擋”,最大的挑戰(zhàn)往往不在于語法,而在于思維模式的轉(zhuǎn)變。

C++ 將內(nèi)存的控制權完全交還給了程序員,這既是絕對的自由,也是沉重的責任。通過本文,我們看到 RAII 賦予了我們確定性的資源釋放能力,而移動語義和智能指針則在“極致性能”與“內(nèi)存安全”之間架起了橋梁。

記住 C++ 現(xiàn)代開發(fā)的黃金法則:默認使用棧對象,堆內(nèi)存首選 unique_ptr,共享資源用 shared_ptr,循環(huán)引用靠 weak_ptr 打破。 掌握了這些,我們就真正駕馭了這門語言最鋒利的雙刃劍。

到此這篇關于C++ 內(nèi)存避坑指南之移動語義和智能指針解決“深拷貝”與“內(nèi)存泄漏”的過程的文章就介紹到這了,更多相關C++ 深拷貝與內(nèi)存泄漏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

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

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

    這篇文章主要介紹了Qt如何使用QPainter實現(xiàn)自定義圓形進度條功能,文中的示例代碼講解詳細,對我們學習Qt有一定的幫助,需要的可以參考一下
    2022-06-06
  • 深入探究C++編程中的資源泄漏問題以及排查方法

    深入探究C++編程中的資源泄漏問題以及排查方法

    在C++程序開發(fā)維護過程中,時常會遇到資源泄漏問題,比如GDI對象泄漏、進程線程句柄泄漏以及內(nèi)存泄漏問題,今天我們就來深入探討一下這幾類資源泄漏以及排查這些泄露的辦法,需要的朋友可以參考下
    2023-10-10
  • C語言動態(tài)內(nèi)存管理分析總結

    C語言動態(tài)內(nèi)存管理分析總結

    C語言中開辟內(nèi)存有很多種方式,目前我們最常用的也就是數(shù)組,但數(shù)組是在我們用到他之前就得設定好它的長度,有時很不方便。隨意我們來探究動態(tài)內(nèi)存管理
    2021-11-11
  • C語言實現(xiàn)撲克牌計算24點

    C語言實現(xiàn)撲克牌計算24點

    這篇文章主要為大家詳細介紹了C語言如何實現(xiàn)撲克牌計算24點,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • C++設計模式之裝飾模式

    C++設計模式之裝飾模式

    這篇文章主要介紹了C++設計模式之裝飾模式,裝飾模式能夠?qū)崿F(xiàn)動態(tài)的為對象添加功能,是從一個對象外部來給對象添加功能,需要的朋友可以參考下
    2014-10-10
  • C++ Qt開發(fā)之使用QHostInfo查詢主機地址

    C++ Qt開發(fā)之使用QHostInfo查詢主機地址

    Qt 是一個跨平臺C++圖形界面開發(fā)庫,利用Qt可以快速開發(fā)跨平臺窗體應用程序,本文將重點介紹如何運用QHostInfo組件實現(xiàn)對主機地址查詢功能,希望對大家有所幫助
    2024-03-03
  • C++淺析內(nèi)聯(lián)函數(shù)的使用

    C++淺析內(nèi)聯(lián)函數(shù)的使用

    為了消除函數(shù)調(diào)用的時空開銷,C++ 提供一種提高效率的方法,即在編譯時將函數(shù)調(diào)用處用函數(shù)體替換,類似于C語言中的宏展開。這種在函數(shù)調(diào)用處直接嵌入函數(shù)體的函數(shù)稱為內(nèi)聯(lián)函數(shù)(Inline Function),又稱內(nèi)嵌函數(shù)或者內(nèi)置函數(shù)
    2022-05-05
  • C語言形參和實參的區(qū)別詳解

    C語言形參和實參的區(qū)別詳解

    在函數(shù)定義和調(diào)用過程中,形參和實參是非常重要的概念,本文主要介紹了C語言形參和實參的區(qū)別,具有一定的參考價值,感興趣的可以了解一下
    2023-05-05
  • 深入分析C++中執(zhí)行多個exe文件方法的批處理代碼介紹

    深入分析C++中執(zhí)行多個exe文件方法的批處理代碼介紹

    本篇文章是對C++中執(zhí)行多個exe文件方法的批處理代碼進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • C++實現(xiàn)LeetCode(241.添加括號的不同方式)

    C++實現(xiàn)LeetCode(241.添加括號的不同方式)

    這篇文章主要介紹了C++實現(xiàn)LeetCode(241.添加括號的不同方式),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-07-07

最新評論

阜城县| 舞钢市| 诏安县| 高雄市| 巴塘县| 东平县| 蒲城县| 阳曲县| 苏尼特右旗| 苏尼特左旗| 靖宇县| 呈贡县| 庆阳市| 青川县| 贞丰县| 尉犁县| 湛江市| 望城县| 新泰市| 寻乌县| 嘉善县| 深水埗区| 漳州市| 花莲县| 万州区| 长沙市| 麻城市| 吐鲁番市| 大厂| 德庆县| 胶州市| 海伦市| 新蔡县| 商河县| 宜春市| 栾城县| 吉安县| 云阳县| 涞源县| 崇阳县| 兴安县|