auto 關鍵字練習題 (Practice - Auto)
Related Concepts
| 關鍵字 | 答案 |
|---|---|
| auto 變數沒有初始化式 | 編譯錯誤——auto 必須初始化 |
持有 closure:auto vs std::function |
auto 較小、較快、可 inline;std::function 可能 heap 配置且較慢 |
unsigned sz = v.size(); |
type shortcut 陷阱:64-bit 平台可能截斷 → 改用 auto |
range-for 遍歷 map 宣告 std::pair<K,V>& |
key 實為 const → 每圈複製暫時物件 → 改用 const auto& |
auto x = vector<bool> 的 operator[] |
推導出 std::vector<bool>::reference(隱形 proxy)→ 可能 UB |
| auto 推導出不想要的型別 | 不放棄 auto,用 auto x = static_cast<T>(expr); |
刻意 double → float / double → int |
用 explicitly typed initializer idiom 宣示意圖 |
| 如何辨識隱形 proxy | 函式庫文件 + 標頭檔函式簽章(回傳型別非慣例 T& 是警訊) |
Question 1 - auto 強制初始化 [recall]
同事宣告
auto x2;打算稍後再賦值,另一行寫int x1;,這兩行分別會發生什麼事?
auto x2; 是編譯錯誤——auto 的型別由初始化式推導,必須初始化。
int x1; 可以編譯,但值可能不確定(視語境而定)。這正是 auto 根絕未初始化變數問題的原因。
Question 2 - auto vs std::function [recall]
要把一個 lambda closure 存進變數,用
auto宣告和用std::function宣告有何差異?
auto 變數的型別就是 closure 本身:記憶體恰好夠用、可 inline、呼叫直接。
std::function 是固定大小的模板實例:closure 裝不下時會heap 配置,呼叫間接、抑制 inlining,幾乎必定較慢,還可能拋 out-of-memory。持有 closure 的競賽中 auto 完勝。
Question 3 - type shortcuts 陷阱 [recall]
unsigned sz = v.size();(v為std::vector<int>)在 32-bit Windows 正常,為何移植到 64-bit Windows 可能出錯?
v.size() 的官方回傳型別是 std::vector<int>::size_type。64-bit Windows 上 unsigned 仍是 32 bits,而 size_type 是 64 bits,可能被截斷。
改寫成 auto sz = v.size(); 就能精確取得 size_type,可攜性無虞。
Question 4 - range-for 隱形複製 [recall]
for (const std::pair<std::string, int>& p : m)遍歷std::unordered_map<std::string, int>,這段程式碼有什麼問題?
hash table 元素的真正型別是 std::pair<const std::string, int>(key 是 const)。型別不符使編譯器每圈複製出暫時物件再讓 p 綁上去,迭代結束即銷毀;取 &p 會得到指向暫時物件的懸空指標。
改用 for (const auto& p : m) 即可直接綁定原元素、零複製。
Question 5 - 重構便利性 [recall]
某函式回傳型別從
int改成long,呼叫端用auto接結果與用int明確宣告接結果,後續維護有何差異?
auto 變數的型別隨初始化式自動更新——下次編譯即自動跟上新型別。
明確宣告 int 的呼叫端則須逐一找出所有呼叫點人工修改。這是 auto 對重構友善的核心理由。
Question 6 - vector<bool> 的 operator[] [recall]
為什麼
std::vector<bool>::operator[]不像其他std::vector<T>一樣回傳T&?它實際回傳什麼?
std::vector<bool> 規定以位元壓縮儲存(一 bit 一個 bool),而 C++ 禁止對位元取參考,無法回傳 bool&。
因此它回傳 proxy 物件 std::vector<bool>::reference——一個「行為像 bool&」、可隱式轉換成 bool 的類別。
Question 7 - 隱形 proxy 家族 [recall]
除了
std::vector<bool>::reference,還有哪些「隱形」proxy class 的例子?它們與智慧指標這類 proxy 有何不同?
例子:std::bitset::reference、expression templates 產生的型別(如 Sum<Matrix, Matrix>)。
智慧指標是顯性 proxy——設計上讓使用者看見並長期持有,與 auto 相容;隱形 proxy 設計上不該存活超過一條語句,用 auto 留住它會違反函式庫設計假設。
Question 8 - 慣用法定義 [recall]
什麼是 explicitly typed initializer idiom?它的名稱由來是什麼?
仍用 auto 宣告變數,但把初始化運算式 cast 成想要的型別:auto x = static_cast<T>(expr);,讓 auto 對 cast 後的結果推導。
名稱由來:變數仍由 auto 推導,但初始器(initializer)的型別被顯式指定——保留 auto 的優點,又拿回型別主導權。
Question 9 - 修正 highPriority [application]
auto highPriority = features(w)[5];(features回傳std::vector<bool>)之後呼叫processWidget(w, highPriority)出現未定義行為,請寫出不放棄 auto 的修正版。
auto highPriority = static_cast<bool>(features(w)[5]); // 強迫推導為 bool
cast 在暫時的 std::vector<bool> 還活著時執行 proxy → bool 轉換,指標仍有效,無 UB。問題不在 auto 本身,而在它忠實推導出了 proxy 型別。
Question 10 - closure 宣告改寫 [application]
C++14 下要寫一個比較函式,能比較任何「像指標」的東西所指向的值(如
std::unique_ptr<Widget>),請用 auto 寫出宣告。
auto derefLess = [](const auto& p1, const auto& p2) // generic lambda
{ return *p1 < *p2; };
closure 的型別只有編譯器知道,auto 是直接持有它的唯一舒適寫法;C++14 更允許 lambda 參數用 auto,一個 closure 通吃所有 pointer-like 型別。
Question 11 - 刻意降精度 [application]
calcEpsilon()回傳double,你確定float精度已足夠。float ep = calcEpsilon();可行但有何不足?如何寫得更好?
隱式 double → float 轉換看不出是刻意為之,讀者可能以為是疏忽。
auto ep = static_cast<float>(calcEpsilon()); // 明確宣示:刻意降精度
explicitly typed initializer idiom 不限 proxy 場景,也用來表達「刻意建立不同型別變數」的意圖。
Question 12 - UB 流程追蹤 [analysis]
假設
std::vector<bool>::reference內含「機器字組指標 + 位元偏移」,請逐步分析auto highPriority = features(w)[5];之後使用highPriority為何是未定義行為。
features(w)回傳暫時物件 temp(std::vector<bool>);temp[5]回傳 proxy,內含指向 temp 內部字組的指標與 offset=5;- auto 推導出 proxy 型別,
highPriority複製了這個 proxy(指標仍指向 temp); - 語句結束 temp 被銷毀 →
highPriority內含 dangling pointer,之後任何使用都是 UB。
對照組bool highPriority = ...安全,因為 proxy 在 temp 存活期間就當場隱式轉換成 bool 值。
Question 13 - expression templates 診斷 [analysis]
數值庫中
Matrix sum = m1 + m2 + m3 + m4;執行正確,改成auto sum = m1 + m2 + m3 + m4;後單元測試離奇失敗。請分析原因,並說明如何從標頭檔預先辨識這類風險。
該庫的 operator+ 為求效率回傳 expression template proxy(如 Sum<Sum<Sum<Matrix,Matrix>,Matrix>,Matrix>)。寫 Matrix sum = ... 時 proxy 隱式轉換成 Matrix;改用 auto 後 sum 就是 proxy 本身,而隱形 proxy 通常不該存活超過一條語句,後續使用行為異常。
辨識方式:讀文件,並檢查函式簽章——回傳型別不是慣例的 T / T&(如 operator[] 回傳 reference 類別)即是 proxy 警訊。
修正:auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);。
| 模式 | 一句話原則 |
|---|---|
| 未初始化風險 | auto 必須初始化 → 根絕不確定值 |
| 冗長 / 寫不出的型別 | iterator value type、closure → auto 一行搞定 |
| 持有 closure | auto 比 std::function 更小、更快、免 heap 配置 |
| type shortcuts | 手寫 unsigned、std::pair<K,V> 會引入隱性轉換 → auto 精確匹配 |
| 重構 | auto 變數隨初始化式型別自動更新 |
| 隱形 proxy | vector<bool>::reference、expression templates 與 auto 天生不合 |
| 解法 | 不放棄 auto:auto x = static_cast<T>(expr); |
| 意圖表達 | 刻意轉型(降精度、取整)也用同一慣用法宣示 |