本篇提到的「語義」,指的就是這些語言機制實際的「作用」或「功能」。
主要討論的四種語義:「pass by value」、「refer to object」、「value」
以及「variable」。
分析一下這四種語義的涵義。
◎四種語義
一、「variable 語義」:
與它對照的是 constant 語義。前者指該語言機制「本身」預設是可變的,
後者則否,例如:
int var = 5; // var 是一個 varaible(變量)
int const c = 3; // c 是一個 constant(常量)
要注意的一點,一個物件(instance)究竟是變量或常量,在 C/C++ 中,
是可以藉由 const 關鍵字來修飾的。
二、「value 語義」
這是一種屬於高級(高階)的功能,指的是物件本身或其所參照的物件,
具有「直接指派(賦值)」的能力。
C 語言,除了 array 之外,都具有 value 語義,在 C++ 中,用戶自訂型別
的 value 語義是透過編譯器預設(或用戶自己實作)的 operator= 來實現。
此外,要特別注意的一點是,指標的 value 語義,是對其本身而言,
但對它所參照(指向)的物件,必須透過 dereference 的動作,才可
以間接 access 該物件的 value 語義(如果有的話)。因此必須特別強
調:「指標對其所參照的物件,並不直接帶有 value 語義」。
由於 value 語義的高級(高階)特性,現代的編程觀念,是儘可能鼓勵
多使用 value 語義,而避免使用隱晦的指標。
三、「refer to object 語義」
指的是指標和 reference 這兩種特殊的機制。事實上只有在 C/C++ 中,
必須特別強調這點(但 C 沒有 reference),因為大部份的新語言是不
支援 pointer 的功能,而傾向把 refer to object 的語義當成內建的
功能。
四、「pass by value 語義」
指的是物件在函式間傳遞時(透過參數或返回值的方式),具有自動被
複製的能力。這一點,C 及 C++ 都相同,例如:
T foo(T var)
{
... // 對 var 進行操作
return var;
}
struct T obj;
T ret = foo(obj);
這個介面,在 foo 函式被喚起時,var 就自動成為外部模組中,obj 的
一個複本(也就是 var 複製了 obj),但它的生存範圍只限於函式 foo
之內,也就是它是一個區域物件。
而,當函式返回時,var 物件又被複製成 ret 物件。obj, var, ret 這
三個物件是三個各自獨立的實體(instance)。
由於 pass by value 語義的特性,對內建型別而言,最有效率,又符合
「模組區域化」的觀念,因此為 C 語言所採用。至於物件之間的溝通,
則以傳遞指標「間接 access」的方式達成。
C++ 保留了 C 語言 pass by value 的特性,並提供 reference 的機制,
顯示的支援 refer to object 語義。一般新的語言則是直接內建 refer
to object 語義,而不(完整)提供 pass by value 的語義(不完整的
意思是,通常只對內建型別支援)。
◎ C++ 各種語言機制,與各種語義的對照
pass by value refer to object value variable
--------------------------------------------------------------------------
object Yes No user-defined Yes
pointer Yes Yes/Inderict self only Yes
reference self only Yes depends on object No
array No No No No/elements only
--------------------------------------------------------------------------
筆者想,應該不會有其他語言(的物件相關機制),比 C++ 更複雜了吧?
在此,逐項檢視 C++ 語言機制的語義特性:
① object:
除了承襲 C 的 pass by value 特性之外,一個 C++ object(或說 C++ class)
是否帶有 value 語義,端視用戶的決定/實現。
C++ 編譯器預設了 memberwise-copy 的方式(而不是 bitwise-copy),可以說
對 value 語義具有相當良好的支援效果,因為只要保證每個成員都帶有 value
語義,則該 class 就自然具備 value 語義。這算是一種相當高級的特性。
② pointer:
檢視上表,不能不佩服 C 語言的偉大,發明了「指標」這種東西!四種重要的
語義,它全都具備(其中 refer to object 是以 dereference 的間接方式來達
成)。也可以說,學不好指標,就學不好 C/C++,不過水能載舟,亦能覆舟,利
弊得失是一體的兩面,自由、不受限制的代價往往就是錯誤、煩惱的根源。
③ reference
可以說是 C++ 對用戶的恩賜,一個簡單,卻是非常重要的機制。新的語言幾乎
都是以內建支援的方式,提供 refer to object 的語義。C++ 保留了 C 的預設
pass by value 語義的優點(彈性),再另外增加 reference 的機制,提供對
refer to object 語義的「直接」支援(指標也支援,但是間接的,容易造成
語法晦澀、複雜、不直觀,及閱讀理解、維護的困難)。
④ array
慘慘慘!看到上表,對 array 來說,真是「怎一個『慘』字了得?」(所以要
三個「慘」字才夠 :) )。
筆者認為,這個表也很明白地反映出,為什麼在 C++ 中,很少鼓勵直接使用
array,而大都會建議新手,以其他具有高級(階)特性的機制來取代 array
(通常是 std::vector)。
首先,C 語言主要的 pass by value 特性,array 沒有,嘗試直接返回一個
陣列,或以參數傳遞,實際作用到的是,array 中第零個元素的位置。返回
區域變量的位址,是一種錯誤的設計,這是新手常犯的錯誤。
其次,現代語言高級的 value 語義,array 也沒有,要複製一個 array,必
須用 memcpy 的方式手動實行 bitwise copy 的動作。
至於 refer to object 及 variable 的特性,就不用多提了,因為這不是使
用 array 時所關心的問題。
array 作為一種最基本的線性、連續(所以支援 random access)的資料結構,
其重要性自不待言,但 C 語言(C++ 為了兼容於 C,所以幾乎延襲其所有特
性,除了 C99 的動態長度之外)所提供的 array,主要是為了效率考量,不
但沒有前面所述的 pass by value 及 value 兩項重要的語義特性,連帶的,
和 C 語言其他變量的規則相同,array 並不會自動初始化。
而實際上,使用 C/C++ array 最危險的一點是,它沒有邊界檢查的功能(也
是為了效率考量),這常常造成不可預期的錯誤,甚至災難。
◎ Object-based(ADT)設計思維的風格
對 C++ 用戶而言,充分理解各種語言機制的特性,與其說是消極地避免犯錯,
不如積極地看成它是 C++ 為用戶提供了「選擇最適當的機制」的自由。能夠良
好掌握並且善用,它就是 advantage;反之,它就變成 trouble。
從比較高階的觀點(也就是 C++ 當初發明的主要目的,支援抽象化的 ADT 機
制)來看,對於本篇所提到的各種機制,在使用上有如下的建議:
①儘可能 prefer 「value 語義」。
簡單的說,只要讓你所有的 class 都帶有 value 語義,那麼,以此組合出來
的 class,就自然帶有 value 語義。value 語義的好處是它排除了直接使用
原始指標所帶來的晦澀和各種風險。至於,當物件複製成本成為效率瓶頸時,
可以利用類似 reference counting 的技巧來實現底層的動作,讓用戶的介面
保持一致,這也是一種優良的設計風格。(較新的語言如 Java, C# 等的好處
就是它們語言本身內建支援這種風格)
②如果 reference 就足夠,儘可能少用 pointer
殺雞焉用牛刀。指標當然是偉大的,但是請多多愛用 reference,它會讓你的
「C++ 程式人生」更愉快。
③如果必須用到 pointer,也不要刻意迴避。
有些語言根本就沒有 pointer 這種東西,不僅簡化了語法,也避免了許多錯
誤的可能。這些語言之所以可以不要 pointer,是因為它們會提供其他方便
的機制來支援大部份常用的功能,例如內建以 pass by reference 的方式
傳遞物件等。
但 C++ 並沒有內建這些機制,假如你就是必須要用到 pointer,也不用刻意
扭曲,讓設計變得不自然。還是一句話,當用則用,指標搞不定,C++ 永遠學
不好。
④儘量謹慎小心 array 的使用
和 pointer 一樣,C/C++ array 搞不定,麻煩也很大。若說要禁用 array,
也太過頭了,還是只能說,沒事(極度效率需求)時,請多愛用 vector,而
當 array 不可避免時,也請用最嚴謹的態度面對它。
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.120.214.120
※ 編輯: cppOrz 來自: 59.120.214.120 (09/02 20:48)