導論與術語練習題 (Practice - Introduction)
Related Concepts
| 關鍵字 | 答案模式 |
|---|---|
「T&& rhs 中的 rhs 是什麼 value?」 |
lvalue——所有 parameter 都是 lvalue |
| 「能否取位址?」 | 能取位址 → 通常 lvalue;不能 → 通常 rvalue(heuristic) |
| 「move 建構出來的物件叫?」 | 仍叫 copy——C++ 不區分 copy-constructed / move-constructed copy |
| 「argument vs parameter」 | argument 可為 l/r value;parameter 恆為 lvalue |
| 「basic vs strong guarantee」 | basic:invariant 完好 + 不漏資源;strong:狀態回到呼叫前 |
| 「書中的 C++11 指?」 | C++11 與 C++14(C++14 是 superset);C++98 含 C++03 |
「signature 含 noexcept 嗎?」 |
不含——只有參數型別 + 回傳型別(本書定義;官方定義有時不含回傳型別) |
| 「deprecated 經典例」 | std::auto_ptr → 改用 std::unique_ptr |
| 「data race」 | ≥2 thread 同時存取同一記憶體位置且至少一 writer → undefined behavior |
| 「closure」 | lambda expression 在 runtime 產生的 function object |
Question 1 - 版本用語 [recall]
書中若寫「C++11 支援 lambda expressions」,這句話涵蓋哪些語言版本?「C++98」一詞又涵蓋哪些?
書中的 C++11 = C++11 + C++14(因 C++14 是 C++11 的 superset);C++98 = C++98 + C++03(兩者僅技術細節不同)。
只有寫 C++14 時才專指 C++14;單寫 C++ 則泛指所有版本。
Question 2 - lvalue 判別法 [recall]
面試官問:「有沒有快速方法判斷一個 expression 是 lvalue 還是 rvalue?這方法絕對可靠嗎?」
取位址判別法:能對 expression 取位址(&)者通常是 lvalue,不能者通常是 rvalue。
這只是 heuristic(書中用 typically / usually),並非嚴格定義。
附帶好處:它提醒你 expression 的型別與其 lvalue/rvalue 身分無關——type T 可以有 lvalue 也可以有 rvalue。
Question 3 - rvalue reference parameter [recall]
Widget(Widget&& rhs);中,rhs的型別是 rvalue reference,那rhs本身是 lvalue 還是 rvalue?
lvalue。所有 parameter 都是 lvalue——rhs 具名、可在 move constructor 內合法取位址。
型別是 rvalue reference 不改變它是 lvalue 的事實(型別與 value category 是兩回事)。
Question 4 - copy 的用語 [recall]
一個物件經由 move constructor 從另一物件建構出來,C++ 術語上這個新物件叫什麼?
仍然叫做原物件的 copy。
C++ 沒有術語區分「copy-constructed copy」與「move-constructed copy」——只要以同型別物件初始化新物件,新物件一律稱為 copy。
Question 5 - exception safety 保證 [recall]
請分別說明 basic exception safety guarantee 與 strong exception safety guarantee 對呼叫者的承諾。
basic guarantee:例外拋出後,程式 invariant 完好(資料結構不毀損)且無資源洩漏——這是 exception safe 的最低門檻。
strong guarantee:例外拋出後,程式狀態回到呼叫前(如同交易 rollback),隱含 basic guarantee。
Question 6 - signature 定義 [recall]
依本書定義,
bool func(const Widget& w) noexcept;的 signature 是什麼?noexcept與參數名w算不算在內?
signature 是 bool(const Widget&)——只包含參數型別與回傳型別。
函式名、參數名(w)不算;noexcept、constexpr 等其他宣告元素也排除在外。
注意:這是 Meyers 為本書方便的定義,官方定義有時不含回傳型別。
Question 7 - deprecated 特性 [recall]
什麼是 deprecated 特性?請舉出書中的經典例子與其替代品,並說明編譯器是否一定會警告。
deprecated = 標準委員會標記為待淘汰(「死刑候審」)的特性,未來標準可能移除。
經典例:std::auto_ptr(C++11 deprecated)→ 改用 std::unique_ptr(同樣工作但做得更好)。
編譯器不一定警告,仍應主動避免——deprecated 特性通常劣於取代它的新特性,且會造成移植困擾。
Question 8 - undefined behavior [recall]
請舉出書中三個 undefined behavior 的典型例子,並給出 data race 的精確定義。
三例:(1) 以 [] 索引超出 std::vector 邊界;(2) 解參考未初始化的 iterator;(3) data race。
data race 定義:兩個以上 thread 同時存取同一記憶體位置,且至少一個是 writer。
UB 意味 runtime 行為完全不可預測,必須避免。
Question 9 - argument 與 parameter [application]
程式碼
Widget wid; someFuncmove(wid));呼叫void someFunc(Widget w;——請指出 argument 與 parameter 各是什麼,並說明w經由何種方式建構。
argument = std::move(wid)(呼叫端 expression,此處為 rvalue);parameter = w(函式內具名變數,恆為 lvalue)。
argument 用來初始化 parameter:因 argument 是 rvalue,w 這個 copy 經由 move construction 建立。
若改傳 someFunc(wid);,argument 是 lvalue,w 則經 copy construction 建立。
Question 10 - 省略號判讀 [application]
你在書中看到
template<typename... Ts> void processVals(const Ts&... params) { … }——其中...與…兩種省略號各代表什麼?
寬省略號 ... 是 C++ 原始碼:variadic template 的 parameter pack 語法(typename... Ts、const Ts&... params)。
窄省略號 … 是書中排版慣例:代表「此處省略其他程式碼」,不是合法 C++。
兩者在閱讀範例時不可混淆。
Question 11 - declaration vs definition 分類 [application]
請將下列四行分類為 declaration 或 definition:
extern int x;、class Widget;、bool func(const Widget& w) { return w.size() < 10; }、enum class Color { Yellow, Red, Blue };
extern int x; → declaration(不配置儲存);class Widget; → declaration(forward declaration)。
bool func(...) { ... } → definition(有函式本體);enum class Color { ... }; → definition(列出 enumerator)。
提醒:definition 同時也算 declaration,反之不然。
Question 12 - copy 成本推理 [analysis]
同事說:「這個物件是另一個物件的 copy,所以建構它的成本就是一次 copy construction。」請分析這句話錯在哪裡,以及要判斷成本還需要哪些資訊。
錯誤在於:copy 一詞不透露建構方式——rvalue 的 copy 一般經 move construction,lvalue 的 copy 一般經 copy construction,成本可能天差地遠。
判斷成本需要兩層資訊:(1) 傳入的 argument 是 lvalue 還是 rvalue(決定走 copy 還是 move);(2) 該型別 move 與 copy 的實際代價(move 未必便宜)。
而且「rvalue → move、lvalue → copy」本身也只是一般情形(generally / usually),不是保證——僅憑「它是 copy」無法做任何成本斷言。
Question 13 - parameter 恆為 lvalue 的深層意涵 [analysis]
「所有 parameter 都是 lvalue」這條規則看似枯燥,卻是 move semantics 與 perfect forwarding 的基石。請分析:若函式的 parameter 型別是
Widget&&,把它直接傳給下一個函式會發生什麼?這解釋了哪些工具的存在必要性?
Widget&& rhs 的 rhs 是具名 lvalue——直接轉傳時會被當成 lvalue,觸發 copy 而非 move,rvalueness 就此遺失。
這正是 std::move 存在的理由:把已知可 move 的 lvalue cast 回 rvalue;而 perfect forwarding(std::forward)則負責保留 argument 原本的 lvalueness / rvalueness 再轉交。
關鍵鏈條:argument(可 l/r value)→ 初始化 parameter(恆 lvalue)→ 需要顯式工具找回 value category。詳見 06-Move-Semantics-And-Perfect-Forwarding/01-Std-Move-And-Std-Forward 與 06-Move-Semantics-And-Perfect-Forwarding/08-Perfect-Forwarding-Failure-Cases。
| 模式 | 一句話心法 |
|---|---|
| value category 與型別無關 | type T 可有 lvalue 也可有 rvalue;取位址是 heuristic |
| parameter 恆為 lvalue | 即使型別是 T&&——這是 std::move/std::forward 存在的根本原因 |
| copy 不透露成本 | move-constructed 也叫 copy;成本取決於 argument 的 value category 與型別 |
| 版本用語有精確語意 | C++98 含 C++03;C++11 含 C++14;寫 C++14 才專指 C++14 |
| exception safety 分兩級 | basic:invariant + 不漏資源;strong:狀態 rollback |
| signature 只含型別 | 參數型別 + 回傳型別;不含名稱與 noexcept/constexpr(本書定義) |
| deprecated / UB 都要避開 | auto_ptr → unique_ptr;越界、未初始化 iterator、data race 皆 UB |
| declaration ⊇ definition | definition 也是 declaration;宣告引入名稱,定義給儲存/實作 |