CppCon 2018 學習:What Do We Mean When We Say Nothing At All?

提供的內容深入探討了C++編程中的一些關鍵概念,特別是如何編寫清晰、易維護的代碼,并展示了一些C++17的新特性。我將對這些內容做中文的解釋和總結。

1. 良好的代碼設計原則

什么是“良好的代碼”?
  • 能工作:代碼實現了預期功能。
  • 能在其他編譯器或平臺上運行:跨平臺的代碼很重要。
  • 能預先回答常見問題:處理好內存管理、邊界情況等問題。
  • 富有表現力:代碼應該易于理解,能清晰表達其目的。
  • 透明且具有溝通性:代碼易于他人理解,代碼意圖清晰。
    這些內容強調了寫清晰且可維護的代碼的重要性,不僅僅是代碼是否能正確工作。

2. Roger Orr 喜愛的代碼片段

通過代碼示例,可以對比如何通過一些小的改進讓代碼變得更加清晰和易于擴展。

原始版本:
class Holder
{
private:int number;
public:Holder(int i);Holder();void inc() { number++; }int getNumber() { return number; }std::string to_string();
};
  • 問題getNumber方法沒有標記為constto_string方法也沒有標記為const或者是override
  • 缺乏清晰的意圖:沒有明確表明哪些方法會修改類的狀態。
改進版本:
class Holder
{
private:int number;
public:explicit Holder(int i);  // 防止隱式轉換Holder();void inc() { number++; }int getNumber() const { return number; }  // 表明不會修改對象狀態virtual std::string to_string() const;  // 用const和virtual標記,確保可以重寫
};
  • 改進點
    • explicit: 防止構造函數進行隱式類型轉換,從而避免一些難以發現的錯誤。
    • const: getNumberto_string方法標記為const,表示這些方法不會改變對象的狀態。
    • virtual: to_string方法標記為virtual,確保子類能夠重寫該方法。

3. C++ 中的對立概念

C++中許多構造都成對出現或者互為對立。我們來看看一些常見的例子。

操作符和括號:
  • 操作符
    • 算術+(加法)與*(乘法)
    • 指針*(解引用)與&(取地址)
  • 括號
    • ()(函數調用)
    • {}(代碼塊/作用域)
    • [](數組下標)
    • <>(模板參數列表)
對立的關鍵字:
  • if / else: 條件執行。
  • noexcept / noexcept(false): noexcept用于聲明一個函數不會拋出異常,noexcept(false)用于聲明該函數可能拋出異常。
沒有對立的部分

一些C++構造沒有直接的“對立”或“反義詞”,如:

  • breakcontinuereturnfoo(x)whileforswitch等控制流語句沒有對立的概念。

4. C++ 17的新屬性(Attributes)

C++17引入了一些新屬性(attributes),比如[[fallthrough]][[maybe_unused]][[nodiscard]],它們有助于改善代碼質量和清晰度。

[[fallthrough]]switch 語句中:

有時你希望故意跳過break語句,使代碼從一個case順利執行到下一個case

switch (i)
{
case 1:
case 2:msg += "case 1 or case 2. ";break;
case 3:msg += "case 3 or ";[[fallthrough]];  // 明確標記是故意的fallthrough
case 4:msg += "case 4.";
default:break;
}
  • 沒有 [[fallthrough]],編譯器可能會警告你關于不小心遺漏break的情況,而加上[[fallthrough]]則表明這是故意的。
[[maybe_unused]]

用于那些可能未被使用的變量或函數,但不希望編譯器發出警告。

[[maybe_unused]] int j = FunctionWithSideEffects();
assert(j > 0);
  • 即使j可能未使用,編譯器也不會對此發出警告。
[[nodiscard]]

此屬性幫助確保函數的返回值不會被忽略。

[[nodiscard]] int getNumber() { return 42; }
auto num = getNumber();  // 正常使用
getNumber();  // 編譯器會警告:返回值被丟棄
  • **[[nodiscard]]**會在函數的返回值被忽略時觸發警告,提醒開發者該返回值可能很重要。

5. 其他重要概念

explicit:

explicit關鍵字用于防止構造函數進行隱式轉換,這樣可以避免一些潛在的錯誤。

constmutable:
  • const:表示方法不會修改對象的狀態,常用于常成員函數或者常量變量。
  • mutable:即使在const對象中,某些成員變量可以被修改,通常用于緩存等需要在不改變對象狀態的情況下修改的變量。
引用限定符(Ref-qualifiers):

引用限定符用于指定一個函數只能在特定的對象類型(左值或右值)上被調用。

void foo() & { /* 只能在左值上調用 */ }
void foo() && { /* 只能在右值上調用 */ }

總結

  1. explicit 與 隱式轉換explicit防止隱式轉換,從而避免一些潛在的隱式轉換錯誤。
  2. constmutableconst確保方法不會修改對象狀態,而mutable用于允許某些成員在const對象中被修改。
  3. [[fallthrough]][[maybe_unused]][[nodiscard]]:這些C++17屬性幫助清晰地表達代碼意圖,并讓編譯器能給出有用的警告。
  4. virtualoverride:通過標記方法為virtual,確保子類能夠重寫該方法;而override則確保方法正確地重寫了基類方法。
    這些特性和原則幫助我們寫出表達性強可維護易于理解的代碼。同時,C++17引入的屬性增強了編譯器的優化能力和錯誤檢查功能,使得代碼更健壯。
    你提供的內容主要是關于如何通過清晰地表達意圖來編寫更易理解和維護的C++代碼。以下是對這些內容的中文解釋和總結:

1. 如何更清楚地表達意圖?

避免默認值(Avoid defaults)
  • 不使用默認參數值:在函數或者類的構造函數中,最好明確指定每個參數,不使用隱式的默認值。這樣可以避免一些意外的行為,讓代碼更易理解。
在類或結構體中始終明確指定 public:private:
  • 即使是一個簡單的兩元素結構體(如 Point),也應明確標注 public:private: 區域。
    struct Point {int x, y;
    public:Point(int x, int y) : x(x), y(y) {}
    private:void privateMethod() {}
    };
    
    • 意圖:明確告知其他開發者哪些成員是公開的,哪些是私有的。
void 函數中加上 return
  • 即便是 void 函數,最好在函數末尾顯式添加 return,這表明你有意結束函數執行。
    void someFunction() {// Do somethingreturn;
    }
    
使用那些可選項(Use optional things)
  • 標記虛函數的重寫:通過 override 明確標示函數是重寫了基類的方法。
    class Base {virtual void foo() {}
    };
    class Derived : public Base {void foo() override {}  // 明確表明這是對基類方法的重寫
    };
    
  • noexcept:如果你明確認為函數不會拋出異常,使用 noexcept 進行標注,增加可讀性并避免誤解。
    void someFunction() noexcept {// No exceptions expected
    }
    
  • 意義:雖然這些關鍵字(如 overridenoexcept)不一定是必須的,但它們提供了重要的意圖信息,幫助讀者理解代碼的設計和限制。

2. 表達意圖的極限

在表達意圖時,有時我們需要在代碼中做出平衡。雖然有很多關鍵字可以使用,但有些關鍵字并不一定出現在C++中,我們也不一定需要它們。

不常見的關鍵字
  • implicit:C++ 沒有類似 implicit 的關鍵字來表示隱式轉換。
  • const(false):沒有這個關鍵字來表示“不可修改”。
  • nonvirtual:C++ 沒有類似 nonvirtual 的關鍵字來禁止虛函數。
  • ByVal:C++ 中也沒有 ByVal 關鍵字來表示按值傳遞。
    我們該如何處理這些情況?
  • 可以通過清晰的命名和注釋來表達意圖。例如,“我知道自己在做什么,請不要修改此部分”

3. 上下文的意義

缺少關鍵字意味著什么?
  • 第一種情況:表示“我已經考慮過這個問題,因此不需要使用關鍵字”。
  • 第二種情況:表示“我從未聽說過這個關鍵字,或者至少沒考慮過它是否應該在此使用”。
    如何傳達給讀者:如果你在代碼庫中始終如一地使用某些關鍵字,那么讀者可以推測你已經考慮過它們的使用。例如,如果你在每個虛函數上都使用 override,那么讀者可以很清楚地理解你有意標明這個函數是重寫的。

4. 注釋的使用

  • 注釋:注釋應僅用于那些可能讓讀者誤解的地方,而不是在每個函數旁邊都加上不必要的注釋。例如,foo 方法看起來像是一個虛擬函數的重寫,但它可能只是一個簽名不同的函數。在這種情況下,你可以加上一些注釋來澄清。
    // 我知道這看起來像是 foo 的重寫,但實際上這是一個不同簽名的函數
    
  • 注釋不是表達意圖的主要方式,而是用于澄清可能的誤解。

5. 可選的返回語句

void 函數中,盡管返回值是 void 類型,但最好明確地加上 return 語句,以表明函數的結束。

void Thimbule(int robbit)
{robbit++;if (robbit)return;robbit--;
}
void Sprial(int oob, int boo)
{oob++;while (true){if (++oob > boo)return;}
}
  • 可選返回語句的好處:在一些復雜的條件分支中,顯式的 return 會使代碼更加清晰,避免不必要的復雜性。

6. 范圍 for 循環(Ranged For)

  • 按值傳遞auto emp : department):
    for (auto emp : department) {// ...
    }
    
    • 適用于當你不需要修改元素時,復制元素的副本。
  • 按引用傳遞auto& emp : department):
    for (auto& emp : department) {// ...
    }
    
    • 用于避免不必要的復制,直接操作元素的引用。
  • 常量引用auto const & emp : department):
    for (auto const & emp : department) {// ...
    }
    
    • 如果不需要修改元素且希望避免復制,可以使用常量引用。

7. 參數傳遞

如何傳遞參數?
  • 按值傳遞Order createOrder(Customer c, OrderItem oi);
    • 適用于參數較小或者需要復制的情況。
  • 按引用傳遞Order createOrder(Customer& c, OrderItem oi);
    • 適用于對象較大,且希望避免復制的情況。
  • 按常量引用傳遞Order createOrder(Customer const& c, OrderItem oi);
    • 如果不需要修改對象,并且希望避免復制,這是最佳選擇。

8. 省略參數名稱

  • 在聲明時,參數名稱可以省略,編譯器并不關心。但人類開發者會關心,因此最好不要省略參數名稱。尤其是在定義函數時,如果某個參數未使用,可以在定義中省略其名稱:
    int DetermineTotalTaxes(int, int, int);
    
    • 但是,在定義時,如果某個參數不使用,最好加上注釋,說明這樣做的原因,避免其他人誤解。
    int DetermineTotalTaxes(int ProvRate, int FedRate, int) {// do somethingreturn 42;
    }
    

總結

  • 表達意圖的清晰性是編寫可維護代碼的關鍵。通過使用適當的關鍵字(如 overridenoexcept 等),標明函數行為,幫助其他開發者更容易理解代碼的設計。
  • 明確代碼意圖:盡量避免默認值,確保參數傳遞方式清晰,函數的返回值明確,并使用注釋澄清潛在的誤解。
  • 一致性:通過在整個代碼庫中一致使用關鍵字和模式,增強代碼的可讀性和可維護性。
    這部分內容探討了如何通過代碼中隱含的設計選擇,傳達更多的意圖和信息,進而提高代碼的可讀性和可維護性。以下是對這些內容的中文解釋:

1. 其他選擇也能傳達大量信息

原始指針是否總是非擁有的指針?
  • bool sendEmails(Employee* pe)Message* sendEmails(Employee* pe)
    • 這些代碼片段的設計中,是否使用了智能指針?
    • 是否頻繁使用了 newdelete
    • 這會涉及到“規則 3 或 5”(Rule of 3/5),即類如果有資源管理行為(如分配內存),它應該提供拷貝構造函數、賦值運算符和析構函數來管理這些資源。
    • 代碼中是否有析構函數?
      傳遞原始指針或智能指針的選擇,能夠傳達代碼的資源管理策略。如果使用了智能指針,代碼可以自動管理內存,減少資源泄漏的風險。如果是原始指針,則意味著可能需要手動管理內存,這就需要仔細考慮拷貝、賦值、析構等操作。
&* 的意義?
  • & 表示引用* 表示指針
    • 傳遞地址或引用是否總是會修改數據?
      • 傳遞引用T&)和傳遞指針T*)都可能導致數據修改,但是否會修改取決于是否是const類型。
      • 在一些編程習慣中,傳遞指針可以暗示轉移所有權,即調用者不再需要負責對象的生命周期,而是交給被調用函數。
      • **“是否擁有”**并不是編譯器關心的問題,但它能表達出代碼設計中的意圖。通過這些選擇,你可以隱式地傳達某個對象是否由當前函數或對象負責管理其生命周期。
傳統的 for 循環是否總是在做一些奇怪的事?
  • 為什么選擇這種循環?
    • 這個問題的重點是:是否需要使用傳統的 for 循環?
      • for 循環在某些情況下非常有用,但它可能會比使用范圍 forfor (auto& emp : department))更復雜且不易理解。選擇傳統的 for 循環可能意味著你在“手動”處理某些特定的邏輯,而不是依賴于標準庫提供的算法。
  • 為什么不使用范圍 for 循環?
    • for 循環能對每個元素進行逐一操作,而 范圍 for 循環(Ranged for)能夠更簡潔地遍歷容器。
  • 是否有算法可以完成這項工作?
    • C++ 標準庫中有許多算法,如 findcountall_ofsort 等,它們提供了比傳統循環更清晰、更簡潔的解決方案。
      結論:使用標準算法而非傳統循環能顯得你對現有工具的理解更深入,表達了你對代碼簡潔性和可讀性的重視。

2. 初始化

  • 構造函數沒有初始化成員變量時的含義
    • 如果構造函數中沒有在 : 后進行成員變量的初始化,可能有以下幾種情況:
      1. 成員變量有非靜態成員初始化器,即它們已經在類中指定了默認值。
      2. 在函數體內進行了初始化,可能是忘記在構造函數的初始化列表中進行初始化。
    • 為什么會這樣?
      • 可能是忘記在構造函數中初始化某些成員。
      • 在多個構造函數中,可能有一個構造函數忘記了初始化某個成員。
        為什么將某個成員初始化為其默認值?
    • 例如,string s = "";vector<Employee> department(0);
    • 這種行為表示可能在某些情況下不需要特別設置成員的初始值,或者是某個“默認”狀態。

3. 語言能否提供幫助?

  • 我們是否應該添加關鍵字或屬性?你會使用它們嗎?
    • implicitconst(false)nonvirtualByVal 等關鍵字,在當前 C++ 標準中沒有,但它們可能能讓代碼更清晰。
    • 為什么不使用 fallthroughmaybe_unused
      • 在 C++17 中引入了 [[fallthrough]][[maybe_unused]] 屬性,這能幫助更清晰地表達意圖:
        • [[fallthrough]] 表示在 switch 語句中的 case 之間有意不加 break,表明是“故意跳過”。
        • [[maybe_unused]] 用來標記那些可能未使用的變量,避免編譯器警告。

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

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

相關文章

C語言中的輸入輸出函數:構建程序交互的基石

在C語言的世界里&#xff0c;輸入輸出&#xff08;I/O&#xff09;操作是程序與用戶或外部數據源進行交互的基本方式。無論是從鍵盤接收用戶輸入&#xff0c;還是將處理結果顯示到屏幕上&#xff0c;亦或是讀寫文件&#xff0c;都離不開C語言提供的輸入輸出函數。本文將深入探討…

高速信號眼圖

橫軸體系時域的抖動大小&#xff1b;縱軸體現電壓的噪聲。 噪聲越大&#xff0c;眼高越小。 抖動越大&#xff0c;眼寬越窄。 眼圖的模板是定義好的最大jitter和噪聲的模板范圍。就是信號的不可觸碰區域。信號波形不能夠觸碰到模板或者進行模板中。也就是眼圖中的線軌跡要在眼…

VisualSVN Server 禁止的特殊符號 導致的。具體分析如下:錯誤提示解讀

是由于 文件夾名稱中包含了 VisualSVN Server 禁止的特殊符號 導致的。具體分析如下&#xff1a; 錯誤提示解讀 錯誤信息明確說明&#xff1a; Folder name cannot contain following symbols < > : " / | and start or end by period. 即 文件夾名稱不能包含以下…

再見,WebSecurityConfigurerAdapter!你好,SecurityFilterChain

對于許多經驗豐富的 Spring開發者來說&#xff0c;WebSecurityConfigurerAdapter 是一個再熟悉不過的名字。在很長一段時間里&#xff0c;它幾乎是所有 Spring Security 配置的起點和核心。然而&#xff0c;隨著 Spring Boot 3.x 和 Spring Security 6.x 的普及&#xff0c;這個…

web前端面試-- MVC、MVP、MVVM 架構模式對比

MVC、MVP、MVVM 架構模式對比 基本概念 這三種都是用于分離用戶界面(UI)與業務邏輯的架構模式&#xff0c;旨在提高代碼的可維護性、可測試性和可擴展性。 1. MVC (Model-View-Controller) 核心結構&#xff1a; Model&#xff1a;數據模型和業務邏輯View&#xff1a;用戶界面展…

【C#】MVVM知識點匯總-2

在C#中實現MVVM&#xff08;Model-View-ViewModel&#xff09;架構時&#xff0c;可以總結以下幾個關鍵知識點&#xff0c;并通過具體的代碼示例來進行說明。 1. 模型 (Model) 模型包含應用程序中的數據和業務邏輯。通常與數據庫交互。 public class User { public int Id {…

一文了解PMI、CSPM、軟考、、IPMA、PeopleCert和華為項目管理認證

1 引言 常見的項目管理方面的認證有PMI、IPMA、PeopleCert、CSPM、軟考和華為項目管理認證6個認證。本篇文章讓你一文了解各認證的基本主要內容。 2 核心定位 目前全球范圍內最具影響力的六大認證體系各有特色&#xff0c;源于不同的管理哲學和實踐背景。六大認證體系的核心…

bean注入的過程中,Property of ‘java.util.ArrayList‘ type cannot be injected by ‘List‘

一、問題 在spring實踐bean注入ArrayList屬性的時候報錯&#xff1a;Property of ‘java.util.ArrayList’ type cannot be injected by ‘List’二、原因分析 在嘗試將 Spring 配置中的 注入到一個 ArrayList 類型的屬性時出現了類型不匹配問題。核心問題在于&#xff1a;Spr…

自注意力機制原理: 向量矩陣案例進行說明

自注意力機制原理: 向量矩陣案例進行說明 目錄 自注意力機制原理: 向量矩陣案例進行說明一個單詞和所有單詞進行乘法運算,提取特征一、場景設定:翻譯句子“我喜歡深度學習”二、向量矩陣構建:以“我”為例計算自注意力三、矩陣視角:批量計算整個序列的自注意力四、向量矩…

D3 面試題100道之(61-80)

這里是D3的面試題,我們從第 61~80題 開始逐條解答。一共100道,陸續發布中。 ?? 面試題(第 61~80 題) 61. D3 中如何繪制餅圖? 使用 d3.pie() 生成角度數據,再結合 d3.arc() 創建路徑。 示例: const data = [10, 20, 30

flutter更改第三方庫pub get的緩存目錄;更改.gradle文件夾存放目錄

1.在目標目錄中新建文件夾flutter_pub_cache 2.在“用戶變量“或“系統變量”中點擊“新建” 變量名: PUB_CACHE 變量值: D:\flutter_pub_cache 3.打開新的終端運行或者從Android studio 控制臺運行&#xff1a;flutter pub cache repair或者flutter pub clean pub讀取新的變…

《Redis》哨兵模式

文章目錄 為什么要有哨兵模式呢&#xff1f;哨兵自動恢復故障主節點使用docker搭建分布式系統查看哨兵節點工作哨兵選舉新的主節點的流程 總結 為什么要有哨兵模式呢&#xff1f; 主從復制的問題 Redis 的主從復制模式可以將主節點的數據改變同步給從節點&#xff0c;這樣從節…

零基礎保姆級本地化部署文心大模型4.5開源系列

近兩年隨著大模型的迅猛崛起&#xff0c;吸引了各行各業的廣泛關注&#xff0c;更對我們的工作方式與生活產生著顯著積極影響。在這樣一個技術范式轉換的關鍵節點&#xff0c;百度文心大模型開源事件無疑具有里程碑意義——它不僅為中國自主研發的AI技術底座打開了通向世界的大…

【筆記】PyCharm 2025.2 EAP 創建 Poetry 和 Hatch 環境的踩坑實錄與反饋

https://youtrack.jetbrains.com/issue/PY-82407/Incorrect-Python-Version-and-Virtual-Environment-Path-When-Creating-Poetry-and-Hatch-Environments-via-GUI-in-PyCharm-2025.2-EAP 在 Python 開發的道路上&#xff0c;PyCharm 一直是我們信賴的開發利器。然而&#xff0…

ASP.NET Web Pages 安裝使用教程

一、ASP.NET Web Pages 簡介 ASP.NET Web Pages 是微軟推出的一種輕量級 Web 開發框架&#xff0c;適合快速開發動態網站。它使用 Razor 語法&#xff0c;可以將 HTML 與 C# 或 VB.NET 無縫融合&#xff0c;特別適合初學者和小型項目。 二、Web Pages 與 MVC 的區別 特性Web …

基于 ethers.js 的區塊鏈事件處理與錢包管理

幣圈工具箱 bqbot.cn 月訪問量達90whttps://bqbot.cn/jms.html &#xff08;在線版地址&#xff09; Event事件 檢索事件 const { ethers } require("hardhat"); async function SearchEvent() {try {const provider new ethers.JsonRpcProvider("http://1…

SpringBoot系列—入門

目錄 1 第一個SpringBoot程序 1.1 創建SpringBoot項目 1.2 選擇SpringBoot版本和必要依賴 1.3 項目目錄結構 1.4 編寫Hello World代碼 1.5 運行程序 1.6 不需要IDEA也能創建SpringBoot程序 1.7 部署程序 1.8 pom.xml依賴問題 1.9 無Maven選項問題 1.10 SpringBoot版…

你的Prompt還有很大提升

與AI協作&#xff0c;Prompt&#xff08;提示詞&#xff09;是溝通的橋梁。一個優秀的Prompt能讓AI的輸出事半功倍&#xff0c;而一個模糊的Prompt則可能導致南轅北轍的結果。如果你覺得AI的回答不夠精準、缺乏深度&#xff0c;或者總帶著一股“AI味”&#xff0c;那很可能是你…

3、Configuring Topics

如果您在應用程序上下文中定義了KafkaAdmin bean&#xff0c;它可以自動向代理添加主題。為此&#xff0c;您可以將每個主題的NewTopicBean添加到應用程序上下文中。2.3版本引入了一個新的類TopicBuilder&#xff0c;使創建此類bean更加方便。以下示例顯示了如何執行此操作&…

FastAPI+React19開發ERP系統實戰第04期

一、效果預覽 1.1 首頁 1.2 首頁暗黑模式 1.3 登錄頁 1.4 登錄頁暗黑模式 二、搭建React開發環境 2.1 項目依賴 package.json {"name": "erp-web","version": "1.0.0","description": "ERP系統前端 - React 19&quo…