術語與慣例 (Terms and Conventions)

Overview Table

術語 一句話定義 關鍵提醒
lvalue 可取得位址的 expression,通常對應具名物件 型別與 value category 無關:type T 可有 lvalue 也可有 rvalue
rvalue 不可取位址的 expression,通常是暫時物件 rvalue 代表可被 move 的物件
argument 呼叫端傳入的 expression 可以是 lvalue 或 rvalue
parameter 函式宣告中的形式參數 一律是 lvalue(即使型別是 T&&
basic guarantee 例外拋出後:invariant 不破壞、資源不洩漏 exception safe 的最低門檻
strong guarantee 例外拋出後:程式狀態回到呼叫前 比 basic 更強,成本可能較高
function object 支援 operator() 的物件(廣義:可用函式呼叫語法者) 狹義來自 C++98,廣義來自 C++11
closure lambda expression 產生的 runtime 物件 書中常將兩者統稱 lambda
declaration / definition 引入名稱 vs 提供儲存/實作細節 definition 也算 declaration
signature 宣告中「參數型別 + 回傳型別」的部分 不含名稱,也不含 noexcept/constexpr
deprecated 標準委員會標記為待淘汰的特性 std::auto_ptr → 改用 std::unique_ptr
undefined behavior (UB) 標準不保證任何 runtime 行為 越界索引、解參考未初始化 iterator、data race
raw / smart pointer 內建指標(如 new 回傳)vs 有智慧的包裝 smart pointer 通常 overload ->*

lvalue vs rvalue:move semantics 的地基

C++11 最普及的特性是 move semantics,其基礎就是區分 expression 是 lvalue 還是 rvalue:rvalue 代表有資格被 move 的物件,lvalue 一般不行。概念上(實務未必),rvalue 對應函式回傳的暫時物件,lvalue 對應可指名參照的物件(具名變數、透過指標或 lvalue reference 取得者)。

實用判別法

能對 expression 取位址(& → 通常是 lvalue;不能取位址 → 通常是 rvalue

class Widget {
public:
    Widget(Widget&& rhs);   // rhs 的「型別」是 rvalue reference,
    // ...                  // 但 rhs 本身是 lvalue(具名、可取位址)
};
兩個必記例外

  1. 取位址判別法只是 heuristic(書中用「typically / usually」),並非嚴格定義。
  2. 所有 parameter 都是 lvalue——即使其型別是 rvalue reference(如上例 rhs)。在 Widget 的 move constructor 內對 rhs 取位址完全合法。這是 Item 23/25 中 std::move 存在的根本原因。

copy 的用語:以同型別物件初始化新物件時,新物件都稱為原物件的 copy——即使它是經由 move constructor 建成的。C++ 沒有術語區分「copy-constructed copy」與「move-constructed copy」。

void someFunc(Widget w);      // w 以 pass-by-value 傳遞

Widget wid;
someFunc(wid);                // w 是 wid 的 copy,經 copy construction 建立
someFuncmove(wid);     // w 仍稱為 wid 的 copy,但經 move construction 建立
成本不可武斷

「rvalue 的 copy 用 move 建構、lvalue 的 copy 用 copy 建構」只是一般情形。若只知道某物件是另一物件的 copy,無法斷言建構成本——必須知道傳入的是 lvalue 還是 rvalue,以及該型別 move/copy 的實際代價。

Argument vs Parameter 與 Exception Safety

argument 是呼叫端傳入的 expression(可為 lvalue 或 rvalue);parameter 是函式內的具名變數(一律是 lvalue)。argument 用來初始化 parameter:

呼叫端                                  函式端
────────                               ────────
someFunc(wid);             argument = wid(lvalue)
someFuncmove(wid));  argument = std::move(wid(rvalue)
        │
        │  初始化
        ▼
void someFunc(Widget w)    parameter = w(永遠是 lvalue)

這個區分在 perfect forwarding 中最關鍵:把 argument 轉交給第二個函式時要保留其 lvalueness / rvalueness(詳見 Item 30 對應筆記)。

設計良好的函式應是 exception safe

保證層級 例外拋出後的承諾 備註
basic guarantee(基本保證) 程式 invariant 完好(資料結構不毀損)、無資源洩漏 exception safe 的最低要求
strong guarantee(強保證) 程式狀態回到呼叫前(如同交易 rollback) 隱含 basic;與 Item 14 的 noexcept、move 策略密切相關

Function Object、Closure 與 Declaration/Definition

名詞 範圍 內容
function object(狹義,C++98) 最窄 支援 operator() 的物件
function object(廣義,C++11) 較寬 可用 名稱(引數) 語法呼叫者:含函式、C 式 function pointer
callable object 最寬 廣義 function object 再加上 member function pointer
closure lambda expression 在 runtime 產生的 function object

實務上可將 function object / callable 統一想成「可用某種函式呼叫語法呼叫的東西」;lambda expression 與其產生的 closure 也很少需要區分(書中統稱 lambda)。同理,function template(產生函式的模板)與 template function(模板產生的函式)亦不刻意區分。

declaration 引入名稱與型別但不給細節;definition 提供儲存位置或實作。definition 同時也是 declaration

extern int x;                      // 宣告:不配置儲存
class Widget;                      // 宣告:forward declaration
bool func(const Widget& w);        // 宣告:無函式本體
enum class Color;                  // 宣告:scoped enum(見 Item 10 對應筆記)

int x;                             // 定義:配置儲存
class Widget { /* ... */ };        // 定義:完整類別
bool func(const Widget& w)
{ return w.size() < 10; }          // 定義:有本體
enum class Color { Yellow, Red, Blue };  // 定義:列出 enumerator

signature(本書定義):宣告中指明參數型別與回傳型別的部分。上例 func 的 signature 是 bool(const Widget&)——函式名、參數名不算,noexceptconstexpr 等元素也排除在外。

與官方定義不同

Meyers 的 signature 定義是為本書方便而簡化;官方定義有時不含回傳型別,細節也不同。面試被問到時要能指出兩者差異。

版本沿革、Deprecated、UB 與指標用語

書中的版本稱呼有精確語意(C++98 與 C++03 僅技術細節不同,故合稱):

書中用語 實際涵蓋版本 範例敘述
C++ 所有版本 C++ 重視效率
C++98 C++98 + C++03 C++98 缺乏 concurrency 支援
C++11 C++11 + C++14(C++14 是 C++11 的 superset) C++11 支援 lambda
C++14 僅 C++14 C++14 支援一般化的函式回傳型別推導
smart pointer 的例外

std::weak_ptr 是例外——它提供 operator* / operator-> 解參考(詳見 Item 20 對應筆記),必須先透過 lock() 取得 std::shared_ptr

書中撰寫慣例速記Widget = 任意 user-defined type;rhs/lhs = 右/左運算元(move、copy 操作與二元運算子的慣用參數名); 窄省略號 = 「此處省略程式碼」,與 variadic template 的 ... 寬省略號不同;ctor/dtor = constructor / destructor 縮寫。

Exam/Test Patterns

情境關鍵字 答案
Widget&& rhs 中的 rhs 是 lvalue 還是 rvalue?」 lvalue——所有 parameter 都是 lvalue,型別是 rvalue reference 不改變這點
「如何快速判斷 expression 是 lvalue?」 能取位址(&)者通常是 lvalue(heuristic,非嚴格定義)
「move constructor 建出來的物件叫什麼?」 仍叫 copy;C++ 不區分 copy-constructed / move-constructed copy
「argument 和 parameter 差在哪?」 argument 是呼叫端 expression(可 l/r value);parameter 是函式內具名變數(恆為 lvalue)
「basic vs strong exception guarantee?」 basic:invariant 完好 + 不漏資源;strong:狀態回到呼叫前
「function object、callable、closure 的關係?」 callable ⊇ 廣義 function object ⊇ 支援 operator() 者;closure 是 lambda 產生的 function object
「本書說 C++11 時包含 C++14 嗎?」 包含——C++14 是 C++11 的 superset;寫 C++14 才專指 C++14
std::auto_ptr 該用嗎?」 不該——C++11 已 deprecated,改用 std::unique_ptr
「data race 的定義?」 ≥2 thread 同時存取同一記憶體位置且至少一個是 writer → undefined behavior
「signature 包含 noexcept 嗎?」 不含——signature 只有參數型別與回傳型別(本書定義)