C++編程實踐--資源管理、標準庫、并發與并行

文章目錄

  • 資源管理
    • 資源訪問
      • 指向資源句柄或描述符的變量,在資源釋放后立即賦予新值
      • lambda函數
        • 當lambda會逃逸出函數外面時,禁止按引用捕獲局部變量
        • 避免lambda表達式使用默認捕獲模式
    • 資源分配與回收
      • 避免出現delete this操作
      • 使用恰當的方式處理new操作符的內存分配錯誤
      • 合理選擇值類型、智能指針、裸指針或引用
      • 使用RAII技術管理資源的生命周期
      • 使用`std::make_unique`而不是`new`創建`std::unique_ptr`
      • 使用`std::make_shared`而不是`new`創建`std::shared_ptr`
  • 標準庫
    • 字符串
      • 1.string_view
      • 2.不要保存std::string類型的c_str和data成員函數返回的指針
      • 3.確保用于字符串操作的緩沖區有足夠的空間容納字符數據和結束符,并且字符串以null結束符結束
      • 4.避免使用atoi、atol、atoll、atof函數
    • 容器與迭代器
      • 1.確保容器索引或迭代器在有效范圍
      • 2.使用有效的迭代器和指向容器元素的指針與引用
      • 3.在不需要修改迭代器指向的對象時,應使用const_iterator
      • 4.確保目的區間已經足夠大或者在算法執行時可以增加大小
      • 5.如果需要刪除容器中的元素,必須在std::remove、std::remove_if類算法之后調用容器的erase方法
    • 禁用rand函數產生用于安全用途的偽隨機數
  • 并發與并行
    • std::thread和std::mutex與std::condition_variable不能拷貝,只能移動
    • 編寫多線程程序必須避免數據競爭
    • 盡量縮短在臨界區內停留的時間
    • 多線程程序中要特別留意對象的生命周期
    • 使用條件變量的wait方法時,必須外加條件判斷,并在循環中等待
    • 不要直接調用mutex的方法
    • 使用C++語言和標準庫的機制實現線程安全的單例初始化
    • 不要在信號處理函數中訪問共享對象

資源管理

資源訪問

  • 外部數據作為數組索引或者內存操作長度時,需要校驗其合法性
  • 內存申請前,必須對申請內存大小進行合法性校驗,防止申請0長度內存,或者過多地、非法地申請內存。
  • 在傳遞數組參數時,不應單獨傳遞指針。當函數參數類型為數組(不是數組的引用)或者指針時,若調用者傳入數組,則在參數傳遞時數組會退化為指針,其數組長度信息會丟失,容易引發越界讀寫等問題。
  • 禁止將局部變量的地址傳遞到其作用域外

指向資源句柄或描述符的變量,在資源釋放后立即賦予新值

“指向資源句柄或描述符的變量”包括:指針、文件描述符、socket描述符以及其他指向資源的變量。

以指針為例,當指針成功申請了一段內存之后,在這段內存釋放以后,如果其指針未立即設置為nullptr,也未分配一個新的對象,那這個指針就是一個懸空指針。
如果再對懸空指針操作,可能會發生重復釋放或訪問已釋放內存的問題,造成安全漏洞。消減該漏洞的有效方法是將釋放后的指針立即設置為一個確定的新值,例如:設置為nullptr。

對于全局性的資源句柄或描述符,在資源釋放后,應該馬上設置新值,以避免使用其已釋放的無效值;對于只在單個函數內使用的資源句柄或描述符,應確保資源釋放后其無效值不被再次使用。
【反例】

int* a = new int{1};
delete a;
...
delete a;  // 錯誤,會導致double free錯誤

【正例】

int* a = new int{1};
delete a;
...
a = nullptr; // 正確
delete a;  // 避免了內存重復釋放

注:默認的內存釋放函數針對空指針不執行任何動作。

【正例】
如下代碼中,在資源釋放后,對應的變量應該立即賦予新值。

Socket s = INVALID_SOCKET;
int fd = -1;
...
CloseSocket(s);
s = INVALID_SOCKET;
...
close(fd);
fd = -1;
...

lambda函數

當lambda會逃逸出函數外面時,禁止按引用捕獲局部變量

如果一個 lambda 不止在局部范圍內使用,禁止按引用捕獲局部變量,比如它被傳遞到了函數的外部,或者被傳遞給了其他線程的時候。lambda按引用捕獲就是把局部對象的引用存儲起來。如果 lambda 的生命周期會超過局部變量生命周期,則可能導致內存不安全。

【反例】

void Foo()
{int local = 0;// 按引用捕獲 local,當函數返回后,local 不再存在,因此 Process() 的行為未定義threadPool.QueueWork([&] { Process(local); });
}

【正例】

void Foo()
{int local = 0;// 按值捕獲 local, 在Process() 調用過程中,local 總是有效的threadPool.QueueWork([local] { Process(local); });
}
避免lambda表達式使用默認捕獲模式

lambda表達式提供了兩種默認捕獲模式:按引用(&)和按值(=)。
默認按引用捕獲會隱式的捕獲所有局部變量的引用,容易導致訪問懸空引用。相比之下,顯式的寫出需要捕獲的變量可以更容易的檢查對象生命周期,減小犯錯可能。
默認按值捕獲會隱式的捕獲this指針,實際等同于按引用捕獲了成員變量。如果存在靜態變量,還會讓閱讀者誤以為lambda復制了一份靜態變量。從C++20開始,通過[=]默認捕獲this將變為deprecated的。所以,當lambda表達式中使用了類成員變量或靜態變量時,不宜使用按值默認捕獲模式。
因此,通常應當明確寫出lambda需要捕獲的變量,而不是使用默認捕獲模式。
【反例】

auto Fun()
{int addend = 0;static int baseValue = 0;return [=]() {                                 // 實際上只復制了addend++baseValue;                               // 修改會影響靜態變量的值return baseValue + addend;};
}

【正例】

auto Fun()
{int addend = 0;static int baseValue = 0;return [addend, value = baseValue]() mutable { // 使用C++14的捕獲初始化一個變量++value;                                   // 不會影響Fun函數中的靜態變量return value + addend;};
}

在 C++ 11 和更高版本中,Lambda 表達式(通常稱為 Lambda)是一種在被調用的位置或作為參數傳遞給函數的位置定義匿名函數對象(閉包)的簡便方法。lambda表達式與任何函數類似,具有返回類型、參數列表和函數體。與函數不同的是,lambda能定義在函數內部。lambda表達式具有如下形式

[ capture list ] (parameter list) -> return type { function body }
  • capture list,捕獲列表,局部變量對于lambda函數體是不可見的,需要通過捕獲的方式獲得。捕獲只針對于lambda函數的作用域內可見的非靜態局部變量。 lambda表達式可以直接使用靜態變量,而不需要被捕獲。lambda函數可以無條件訪問全局變量、作用域內的靜態變量。捕獲可以分為按值捕獲和按引用捕獲。
  • parameter list,參數列表。從C++14開始,支持默認參數,并且參數列表中如果使用auto的話,該lambda稱為泛化lambda(generic lambda);
  • return type,返回類型,這里使用了返回值類型尾序語法(trailing return type synax)。可以省略,這種情況下根據lambda函數體中的return語句推斷出返回類型,就像普通函數使用decltype(auto)推導返回值類型一樣;如果函數體中沒有return,則返回類型為void。
  • function body,與任何普通函數一樣,表示函數體

資源分配與回收

  • new和delete配對使用,new[]和delete[]配對使用
  • 自定義new/delete操作符需要配對定義,且行為與被替換的操作符一致

避免出現delete this操作

delete this操作是自己銷毀自己,在此之后再訪問到該對象的成員時,可能造成未定義行為。
【例外】
在資源管理器、生命周期管理器等場景中可以使用delete this操作,此時應滿足在delete this 操作后不再提供任何能夠訪問到this的入口,并且被delete的對象是由普通的new分配的。

使用恰當的方式處理new操作符的內存分配錯誤

默認的new操作符在內存分配失敗時,會拋出std::bad_alloc異常,而使用了std::nothrow參數的new操作符在內存分配失敗時,會返回nullptr
因此,需要針對不同場景來處理new操作符的內存分配錯誤:

  • 對于不會返回nullptrnew操作,不要對返回值做空指針檢查。如果new操作失敗拋出異常,則不會執行后面的代碼,因此檢查空指針是多余的操作。
  • 對于可能會返回nullptrnew操作,與對待malloc等內存分配函數一樣,需要對返回值做空指針檢查,如:使用了std::nothrownew操作

合理選擇值類型、智能指針、裸指針或引用

通用原則:

  • 使用值類型 T 或 unique_ptr 來表達獨占所有權
  • 如果需要轉移所有權,應使用智能指針,而不是使用T*或T&作為參數
  • 原生指針 T* 和引用 T& 不表達所有權概念
  • 不涉及所有權轉移的場景,應優先使用T*或T&作為參數,而不是智能指針。例如:不應使用 const unique_ptr& 類型作為參數
  • 當函數的返回類型為T*時,應當表示一個位置,而非傳遞所有權。返回的指針所指向的對象必須在調用者的作用域內有效。如果返回值不可能為空,則優先返回引用

智能指針:

  • 使用 shared_ptr<T> 來表達共享所有權。如果資源只有一個所有者,應使用unique_ptr<T> 而不是shared_ptr<T>
  • 使用 unique_ptr<T> 作為函數的參數和返回值,代表所有權轉移
  • 使用 shared_ptrunique_ptr 代替 auto_ptrauto_ptr在C++11中已標識為deprecated,在C++17中已去除
  • 使用智能指針時也需要注意對象的生命周期,例如:使用get()返回的指針時,1)如果智能指針釋放了其管理的對象,則該指針變成了無效指針;2)不能使用該指針初始化另一個智能指針;…

函數參數:

  • 使用 T&& 或者 unique_ptr<T> 類型作為參數,代表這個資源的所有權是從外部移動進來的
  • 使用 T 類型做為參數,代表這個函數內部擁有資源的所有權,資源可能是拷貝或者移動進來的
  • 使用 unique_ptr<T>& 類型作為參數,代表這個函數可能重置這個 unique_ptr 的指向
  • 使用 shared_ptr<T> 類型作為參數,代表這個函數也是這個資源的其中一個擁有者
  • 使用 shared_ptr<T>& 類型作為參數,代表這個函數可能重置這個 shared_ptr 的指向
  • 使用 const T& 類型作為參數,代表這個函數對資源是只讀的,且不管理資源的釋放
  • 使用 T& 類型作為參數,代表這個函數對資源可讀寫,且不管理資源的釋放
  • 使用 const T* 類型作為參數,代表這個參數可能為空,這個函數對資源是只讀的,且不管理資源的釋放
  • 使用 T* 類型作為參數,代表這個參數可能為空,這個函數對資源可讀寫

數組和字符串:

  • 使用 T*T& 作為參數,代表指向的是一個 T 元素,而不是一組元素。即便是指針指向一組元素中的其中一個,也不應使用指針算術運算指向其他元素。如果要表達指向的是一組元素,應明確表達這個區間的開始和結束
  • 如果是表達字符串類型,應優先使用 std::stringstd::string_view(C++17)或類似的自定義類型
  • 如果是表達固定大小的數組類型,應優先使用 std::arraystd::span(C++20)或類似的自定義類型

使用RAII技術管理資源的生命周期

RAII代表 resource acquisition is initialization。它可以用于避免手工資源管理的復雜性。

資源的獲取和釋放是成對操作(例如new/delete,fopen/fclose,lock/unlock 等),恰好能對應C++語言對稱的構造函數和析構函數。利用C++對象的生命周期來管理資源的生命周期,是一種常見的策略。

使用std::make_unique而不是new創建std::unique_ptr

本條款適用于C++14及之后的版本。C++14開始增加了std::make_unique,提供與std::make_shared類似的方式構造unique_ptr
相對于先 new 出裸指針再構造 unique_ptr ,直接使用 make_unique 的優點有:

  • make_unique 可以更明確的避免裸指針和智能指針混用。
  • 使用 make_unique 更簡潔。

【例外】
因為技術原因,希望使用 unique_ptr、又無法使用 make_unique 的,可以不使用 make_unique
目前的已知場景有:

  • 使用 make_unique 時,不支持自定義 deleter。在需要自定義 deleter 的場景,建議在自己的命名空間實現定制版本的 make_unique
  • 如果分配內存需要自定義的內存分配方式(如使用 placement new、nothrow版本的new等)的話,也沒法直接使用 make_unique。一種實際的場景是如果想對 C 的變長結構體(尾項為靈活數組成員)使用 unique_ptr,需要先分配比結構體大小更大的內存空間,然后使用 placement new 來進行初始化操作。對于這種情況,建議把 unique_ptr 的創建封裝到一個單獨的函數里。
  • C++20 之前不能用 std::make_unique 對沒有構造函數的 C 結構體進行聚合初始化。可以考慮使用下面的自定義版本。
// C++17 的 std::make_unique 不能調用聚合初始化(C++20 里已解決);下面的工具函數解決了這個問題
template <typename T, typename... Args>
std::unique_ptr<T> MakeUnique(Args &&... args)
{if constexpr (std::is_constructible_v<T, Args...>) {return std::unique_ptr<T>{new T(std::forward<Args>(args)...)};} else {return std::unique_ptr<T>{new T{std::forward<Args>(args)...}};}
}

使用std::make_shared而不是new創建std::shared_ptr

std::shared_ptr管理兩個實體:

  • 控制塊(存儲引用計數,deleter等)
  • 管理對象

std::make_shared創建std::shared_ptr,會一次性在堆上分配足夠容納控制塊和管理對象的內存。 而使用std::shared_ptr<SomeClass>(new SomeClass)創建std::shared_ptr,除了new SomeClass會觸發一次堆分配外,std::shard_ptr的構造函數還會觸發第二次堆分配,產生額外的開銷。

【例外】
類似std::make_unique,因為技術原因,希望使用 shared_ptr、又無法使用 make_shared 的,可以不使用 make_shared

標準庫

字符串

1.string_view

C++17開始增加了std::string_view類型,該類型可以減少字符串復制操作,提升程序性能。在C++17及之后的版本中,建議使用std::string_view表示字符串常量,在C++17之前可以使用C風格的字符串常量。

當函數參數為只讀字符串時,在C++17及之后的版本中使用std::string_view類型。

void Fun(std::string_view str) {...
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96192.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96192.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96192.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

“R語言+遙感”的水環境綜合評價方法實踐技術應用

專題一、R語言概述1.1 R語言特點&#xff08;R語言&#xff09;1.2 安裝R&#xff08;R語言&#xff09;1.3 安裝RStudio&#xff08;R語言&#xff09;&#xff08;1&#xff09;下載地址&#xff08;2&#xff09;安裝步驟&#xff08;3&#xff09;軟件配置1.4 第一個程序He…

【項目復盤】【四軸飛行器設計】驅動開發部分

由于在參加面試時總需要花時間一點一點的回憶自己的項目內容&#xff0c;故我打算直接寫一系列的項目復盤博客&#xff0c;方便每次面試前的回憶。內容僅作分享交流&#xff0c;如有謬誤歡迎指正。 本項目系列的文章目錄如下&#xff1a; 【項目復盤】【四軸飛行器設計】驅動…

wpf之ComboBox

前言 wpf中ComboBox的應用非常廣泛&#xff0c;本文就來介紹ComboBox在wpf中的應用。 1、非MVVM模式下 1.1 xaml添加元素<ComboBox x:Name"cbx_test1" SelectedIndex" 0" ><ComboBoxItem >小明</ComboBoxItem ><ComboBoxItem &g…

從零開始學AI——13

前言 夏天快要過去&#xff0c;本書也快接近尾聲了。 第十三章 13.1 半監督學習 在此之前&#xff0c;我們討論的所有學習范式都具有非常明確的邊界條件&#xff1a; 監督學習&#xff1a;我們擁有大量帶標簽的數據樣本(xi,yi)(x_i, y_i)(xi?,yi?)&#xff0c;目標是學習從輸…

k8sday12數據存儲(1/2)

目錄 一、簡單基本存儲 1、EmptyDir 1.1概念 1.2作用 1.3配置文件 1.4測試 2、HostPath 2.1概念 2.2作用 2.3配置文件 2.4測試 ①、數據共享 ②、持久化存儲 3、NFS 3.1概念 3.2作用 3.3NFS服務安裝 ①、設置主節點為NFS服務器 ②、給副節點安裝NFS客戶端工…

Spring Framework 常用注解詳解(按所屬包分類整理)

在使用 Spring Framework 進行開發時&#xff0c;注解&#xff08;Annotation&#xff09;是實現 依賴注入&#xff08;DI&#xff09;、組件掃描、AOP 切面、事務管理 和 Web 請求映射 的核心手段。Spring 提供了豐富且結構清晰的注解體系&#xff0c;這些注解按照功能被組織在…

ROADS落地的架構藍圖

2 ROADS落地的架構藍圖 將ROADS體驗從理念轉化為現實&#xff0c;需要一套完整且自頂向下的架構藍圖作為支撐。華為的實踐表明&#xff0c;數字化轉型的成功依賴于多個架構層次的協同推進&#xff0c;而非單點技術的應用。該藍圖通常包含以下五個關鍵層次&#xff0c;每一層都承…

如何構建一個神經網絡?從零開始搭建你的第一個深度學習模型

在深度學習的海洋中&#xff0c;神經網絡就像一艘船&#xff0c;承載著數據的流動與特征的提取。而構建一個神經網絡&#xff0c;就像是在設計這艘船的結構。本文將帶你一步步了解如何使用 PyTorch 構建一個完整的神經網絡模型&#xff0c;涵蓋網絡層的組織、前向傳播與反向傳播…

自學嵌入式第二十三天:數據結構(3)-雙鏈表

一、strtokchar * strtok(char *s1,char *s2);截斷字符串&#xff0c;在s1字符串中找到s2截取前一段返回&#xff0c;如需要再次截取剩余段&#xff0c;再使用此函數s1輸入NULL即可&#xff1b;二、bzerobzero(char *p,size_t size);清零,從p地址開始&#xff0c;清零size個bit…

河南萌新聯賽2025第六場 - 鄭州大學

暑期集訓已經接近尾聲&#xff0c;一年六場的暑期萌新聯賽也已經結束了&#xff0c;進步是比較明顯的&#xff0c;從一開始的七八百名到三四百名&#xff0c;雖然拿不出手&#xff0c;但是這也算對兩個月的集訓的算法初學者的我一個交代。 比賽傳送門&#xff1a;河南萌新聯賽…

2-1.Python 編碼基礎 - 基礎運算符(算術運算符、賦值運算符、比較運算符、邏輯運算符)

一、算術運算符 1、基本介紹編號運算符說明示例輸出結果1兩數相加10 20302-兩數相減10 - 20-103*兩數相乘&#xff0c;或者返回一個被重復若干次的字符串10 * 202004/兩數相除10 / 200.55//兩數相除并返回商的整數部分9 // 246%兩數相除并返回余數10 % 507**冪運算10 ** 21002…

CMOS知識點 MOS管不同工作區域電容特性

知識點14&#xff1a;MOSFET的電容主要來源于其物理結構&#xff1a;柵氧層電容&#xff1a;柵極&#xff08;G&#xff09;與襯底&#xff08;B&#xff09;、溝道、源&#xff08;S&#xff09;、漏&#xff08;D&#xff09;之間隔著二氧化硅絕緣層&#xff0c;自然形成電容…

預測性維護+智能優化:RK3568+FPGA方案在儲能行業的應用

在儲能行業&#xff0c;RK3568FPGA方案通過預測性維護和智能優化技術&#xff0c;顯著提升系統可靠性和經濟性。該方案采用異構架構&#xff08;FPGA處理高速信號采集&#xff0c;RK3568負責策略計算與通信管理&#xff09;&#xff0c;實現微秒級響應和精準控制。?26一、預測…

工業4.0時代,耐達訊自動化Profibus轉光纖如何重構HMI通信新標準?“

在智能制造與工業4.0浪潮下&#xff0c;HMI&#xff08;人機界面&#xff09;作為設備與操作員之間的“橋梁”&#xff0c;承擔著實時數據顯示、設備監控及交互控制的核心職能。然而&#xff0c;傳統Profibus總線在HMI連接中常因電磁干擾、傳輸距離限制等問題&#xff0c;導致畫…

SpringClound——網關、服務保護和分布式事務

一、網關網絡的關口&#xff0c;負責請求的路由、轉發、身份驗證server:port: 8080 spring:cloud:nacos:discovery:server-addr: 192.168.96.129:8848gateway:routes:- id: item-serviceuri: lb://item-servicepredicates:- Path/items/**,/search/**- id: user-serviceuri: lb…

【C++】模版(初階)

目錄 一. 函數模版 1. 格式 原理 2. 函數模版的實例化 二. 類模板 void Swap(int& left, int& right) {int temp left;left right;right temp; }void Swap(double& left, double& right) {double temp left;left right;right temp; }void Swap(char&…

InfluxDB 開發工具鏈:IDE 插件與調試技巧(二)

四、利用 IDE 插件提升開發效率 4.1 代碼編寫技巧 在使用安裝了 InfluxDB 插件的 IDE 進行代碼編寫時&#xff0c;我們可以充分利用插件提供的代碼導航和智能提示功能&#xff0c;來顯著提高編寫 InfluxDB 相關代碼的效率和準確性。 以一個涉及多個 Measurement 和復雜查詢條…

定制開發開源AI智能名片S2B2C商城小程序:場景體驗新維度與四重目標達成

摘要&#xff1a;本文聚焦于定制開發開源AI智能名片S2B2C商城小程序&#xff0c;探討其在場景體驗領域的應用與價值。通過深入分析場景體驗的最高境界——深體驗、強認知、高傳播、關系深化這四個目標&#xff0c;闡述該小程序如何憑借自身特性與功能&#xff0c;在商業場景中實…

開源 GIS 服務器搭建:GeoServer 在 Linux 系統上的部署教程

GeoServer 是一個開源的地理信息服務服務器&#xff0c;可以發布地圖、矢量數據和柵格數據。 1. 更新系統 sudo apt update && sudo apt upgrade -y2. 安裝 Java 11 GeoServer 需要 Java 運行環境&#xff0c;這里用 OpenJDK 11。 sudo apt install openjdk-11-jdk…

前端面試通關:Cesium+Three+React優化+TypeScript實戰+ECharts性能方案

前端面試題詳解與更多面試題 WebGLCesiumThree 1. 自我介紹 回答要點&#xff1a; 教育背景和工作經驗技術棧和專長領域參與過的重點項目個人優勢和學習能力職業規劃 示例&#xff1a; “我是一名有前端開發經驗的工程師&#xff0c;熟練掌握React、Vue等主流框架&#x…