譯自:http://www.caravan.net/ec2plus/guide.html
A. 代碼從C語言到C++語言的移植
A.1 字符常量
注解
在C中,字符常量是int類型,而在C++中,其類型為char.
示例
I = sizeof(‘a’);
在C中,i保存的實際內容是sizeof(int)的計算結果值,該值要比1大。而在C++中,i存儲的實際內容是sizeof(char),該值總是為1。
準則
當將C代碼移植為C++代碼時,所有對字符常量的sizeof有依賴關系的表達式都要被移除。
A.2 文件范圍內的對象聲明
注解
在C++中,在文件范圍內,聲明一個沒有指定存儲類型標志符的對象時,實際上是定義該對象為外部鏈接符號(extern)。如果這個定義中沒有初始化表達式,那么該對象將被初始化為0。相比較于C來說,C++程序中聲明的對象會被明確的定義且只定義過1次。
而在C中,這將被視為一個暫時的定義,這種定義方式在一個translation unit中允許出現多次。
示例
int a; /* (1) */
int a = 10; /* (2) */
在C中,(1)是一種暫時性的定義。由于(2)被看作是確切的定義,(1)被看作是只是聲明一個變量。
在C++中,(1)、(2)都被認為是定義式。由于存在(1)、(2)兩個重復定義式,因此是一個錯誤。
準則
在C++中,如果想在文件范圍內僅聲明一個對象(而不是定義該對象),那么該對象不能擁有初始化表達式,并且需要使用extern來修飾。
聲明于文件范圍內的每一個對象只能顯式的被定義一次。除了一次聲明外,其余所有聲明都必須具有extern修飾并且沒有初始化表達式。
A.3 const 類型修飾符
注解
在C中,在文件范圍內,一個使用const修飾而未指明存儲類型的對象具有外部鏈接屬性。而在C++中,它具有內部連接屬性。
示例
+- file1 --------------------+
| extern const int n; |
+----------------------------+
+- file2 --------------------+
| const int n = 10; |
+----------------------------+
在C中,文件file2中的對象n具有外部連接屬性,因此,文件file1中對象n(該對象同樣具有外部鏈接屬性)可以引用它。在C++中,文件file2中的對象n具有的屬性是內部鏈接的,因此,file1中的對象n無法引用到它。
準則
使用extern顯示修飾const對象,使該對象具有外部鏈接屬性。
A.4 void*的轉型
注解
C語言標準允許void*轉換為T*(T表示任何對象),而在C++中沒有這樣的標準。在C++中類似的轉換需要顯示轉換來完成。
下面這些C標準庫函數都返回void*:
calloc, malloc, realloc, bsearch, memcpy, memmove,
memchr, memset
在C++中,將這些函數的返回值賦值給一個非void*的指針時,需要
顯示轉換。
示例
int* p;
p = malloc(10 * sizeof(int));
在C++中,對指針p的賦值需要顯示的轉換,如:
p = (int *)malloc(10 * sizeof(int));
準則
在C++中使用new來代替calloc、malloc、realloc(參考A.12);
忽略memcpy, memmove, 以及memset的返回值(通常這些返回值由函數的第一個參數轉換而來);對于其他所有返回void*函數(包括標準庫函數和用戶自定義函數),需要使用顯式的轉換來將返回值轉換為其他指針類型。
A.5 枚舉類型
注解
C中的枚舉變量是整型。程序中,枚舉類型和整型可以互相轉換,而不需要顯式的轉換。C程序允許枚舉類型對象進行++和--運算。
C++中的枚舉是一種用戶自定義類型。C++標準允許枚舉類型轉換為整型,但從整型轉換為枚舉類型是非法的。C++程序中還不能將內置的++和—以及符合運算符(如+=)作用于枚舉類型變量上。
示例
enum RGB { red, green, blue } rgb;
++rgb;
如果表達式(++rgb)采用內置的++運算符,那么在C++中這是一個錯誤表達式。該表達式的語意相當于:
rgb = rgb + 1;
上面的表達式在C++中還是錯誤的。而像下面這樣,對枚舉值進行顯示的轉換就是正確的:
rgb = RGB(rgb + 1);
最好的解決方法是為枚舉類型RGB實現一個++運算符。
RBG &operator++(RGB &x)
{
return x = RGB(x + 1);
}
準則
將C程序移植為C++程序時,在需要的時候,要為枚舉類型實現類型安全的++和--操作符。
A.6 在轉型、參數聲明、sizeof中定義類型
注解
在C中,轉型表達式、參數聲明以及sizeof表達式中都可以進行類
型聲明,而在C++中則不能。
示例
void func(struct TAG { int a; } st)
{
...
}
如上所示,TAG在參數聲明是被定義。
準則
把在參數中聲明的類型的定義式,挪到函數聲明式作用域開始處,或者大于該作用域的某處。
在轉型和sizeof表達式作用域開始處,或者大于該作用域的某處定義類型。
A.7 忽略局部對象定義式的控制流程跳轉
注解
在C中,goto和switch語句可以使控制流程跳過塊作用域內的局
部對象聲明式,甚至有可能是對象的初始化式。而在C++中不存在這種跳轉。
示例
goto LABEL;
{
int v = 0;
...
LABEL:
...
}
在C中,上面的代碼是合法的,它假設標簽LABEL之后的代碼不依賴于整型變量v的初值0。而在C++中,這段代碼總是不能通過編譯。
準則
確保goto或switch語句沒有跳過局部對象的初始化式。
A.8 字符數組的初始化
注解
在C中,允許使用字符串來初始化一個字符數組。初始化表達式中,數組可以比字符串少一個字符空間(‘\0’字符)。在C++中數組則必須能完全容納字符串。
示例
char s[3] = "abc";
盡管常量字符串為4個字符大小,但數組s的大小為3。這種做法在C中合法,而在C++中非法。
準則
為了容納‘\0’,請確保字符數組大小要比字符串長度大1。因而,有必要將字符數組大小指定為字符串長度+1。(也即:char s[4] = "abc";)
然而,為了使定義式自適應于不同的常量字符串,在定義式中不指定數組大小不失為一種好方法(即:char s[] = "abc";)。
A.9 原型聲明
注解
C++程序要求在使用函數之前必須聲明該函數原型。此外,C++程序會將函數聲明式f()解釋為f(void)----不帶參數的函數。而在C中,這樣的行為是不確定的。
示例
extern void func();
....
sub();
func(0);
因為沒有sub函數的聲明,因此調用該函數是錯誤的。由于聲明式中函數不帶參數,因此以0為參數調用func也是個錯誤。
準則
確保被調用的函數已經被聲明。為了強調函數f不帶參數,好的做法是在聲明函數時,將參數聲明為void,即聲明式為:f(void).
A.10 C++增加的關鍵字
注解
C中沒有下面的C++關鍵字:
asm bool catch class
const_cast delete dynamic_cast explicit
false friend inline mutable
namespace new operator private
protected public reinterpret_cast
static_cast template this throw
true try typeid typename
using virtual wchar_t
示例
int class, new, old;
準則
確保沒有使用C++關鍵字作為標記符。
A.11 嵌套類型的作用域
注解
C結構體或聯合體內定義嵌套類型,該類型的作用域終止點和該結構
體或聯合體相同。而C++中定義的嵌套類型作用域終結于該結構或聯合體。
示例
struct S {
int a;
struct T {
int t;
} b;
int c;
enum E { V1, V2 } e;
};
struct T x;
enum E y;
x、y的聲明在C程序中是合法的,但在C++中非法。在C++程序中,
在類S中定義的類型T、E的作用域不會超過類S的定義范圍。
準則
除非只是在結構體或聯合體內使用定義的嵌套類型,否則不在嵌套作用域內定義類型。
A.12 動態內存管理
注解
new/delete和malloc/free沒有采用相同的內存管理策略。因此,
除非已經使用new獲取了內存,否則應用程序中不能使用delete來釋放內
存。同樣,除非使用了malloc分配內存,否則不能使用free來釋放內存。
示例
int (*p)[10];
p = (int (*)[10])malloc(sizeof(*p));
....
delete p;
這里的delete具有未定義的行為。
準則
在C++中避免使用malloc/calloc/realloc/free函數,而僅使用
new/delete.
A.13 '/'之后的'/*'
注解
C++程序具有‘//’的注釋風格,因此緊接在符號‘/’之后的C風格
注釋‘/**/’,會被C++程序作為理解為‘//’后接‘**/’。
示例
i = j //* comment */ k ;
‘//’被解釋為注釋分隔符,因而表達式被解釋為‘i=j’而不是‘i=j/k’。
準則
盡量避免緊接著符號‘/’后寫C風格注釋‘/**/’.
B. 關于代碼容量的準則
B.1 對象初始化
注解
有很多方法來初始化某個對象,有些初始化方法將會產生不必要的
臨時對象,從而導致代碼量膨脹。
例如:
T x(i) // (1)
T x = i; // (2)
T x = T(i) // (3)
T x; // (4)
x = i; //
(1) 直接使用構造函數來初始化對象x,這樣就不會產生臨時對象。
調用形式類似于:
x.T(i); // x對象調用構造函數
(2) 在某些實現中,方法(2)類似于方法(1),即對象x直接調用
構造函數。而在另外一些實現中,該方法會先使用構造函數生成一個臨時對象,然后將該臨時對象作為對象x的初始值。調用形式類似于:
temp.T(i); // 生成temp
x.T(temp); // x調用拷貝構造函數
temp.~T(); // 析構temp
(3) 等同于(2)
(4) 使用T的默認構造函數來初始化x,然后調用賦值操作符將新值
賦給x。賦值操作符可能會釋放x正在使用的資源,并重新為x獲取新的資源。
x.T(); // x調用默認構造函數
x.operator=(i); // x調用賦值操作符
準則
在上面的四種方法中,優先考慮方法(1)。
B.2 inline標記符
注解
內聯減少了函數進出棧上的管理開銷,但這也同時增加了代碼容量。
在內內部定義的成員函數默認是內聯的。
準則
僅對小函數進行inline修飾。在類體之外定義所有不適合作為內聯函數的成員方法,從而使得該成員不是內聯函數。
B.3 返回值中的臨時對象
注解
函數按值返回一個對象時,可能需要創建并銷毀一個臨時對象,從而
導致代碼量增加并且帶來運行時開銷。
示例
class Matrix {
int a, b;
public:
Matrix &operator+=(const Matrix &);
friend
Matrix operator+(const Matrix &, const Matrix &);
};
Matrix operator +(const Matrix &, const Matrix &)
{
...
}
void func()
{
Matrix a,b;
a = a + b; // (1)
a += b; // (2)
}
函數func在(1)處調用了+操作符,該操作符按值返回一個Matrix
對象。在某些編譯器實現中,會先構造一個Maxtrix臨時對象,然后銷毀
該臨時對象。
函數func在(2)處調用+=操作符,該操作符返回一個Matrix對象
引用,因而不會構造臨時對象。
準則
對于類類型對象,使用復合賦值操作符(如使用‘+=’而不是‘+’和
‘=’)來避免編譯器生成再銷毀不必要的臨時對象。
注:個人認為這個準則有失準確。此例中,+=操作符不產生臨時對象的原因在于該操作符按引用返回對象而非按值返回對象。因此,準確的說法是,使用按引用或指針返回對象的函數來避免構造不必要的臨時對象。
B.4 new和delete操作符
準則
在必要時,實現類屬的new和delete操作符,從而提升管理動態內存
的速度和內存利用率。
B.5 全局對象的初始化
注解
全局對象初始化的順序依賴于編譯器的實現。但可以肯定的是,在同一個解釋單元其初始化順序和對象的聲明順序相同。
示例
文件1 文件2 文件3
int a = f(); int b = f(); int f(void)
{
static int a = 0;
return a++;
}
程序可能將a初始化為0,而b為1,或者反過來。這依賴于編譯器如何選擇他們的初始化順序。
如果將文件2中變量b的聲明移到文件1中,那么兩變量的初始化順序就隨即確定了,即:
文件1 文件2 文件3
int a = f(); int f(void)
int b = f(); {
static int a = 0;
return a++;
}
這種情況下,a要先于b被初始化。
避免編寫依賴于不同解釋單元內全局對象初始化順序的代碼。
C. 關于速度的準則
C.1 元素為類對象的數組中的new/delete
注解
聲明元素為類對象的數組時,編譯器會為該數組每一個元素調用構造
函數。在超出該數組的作用域范圍時,又會一一調用每個元素的析構函數。構造/析構可能占用超乎想象的時間,這在實時過程系統中是一個問題。
準則
在對時間要求比較高的過程系統中,避免創建/銷毀大型的元素為類對象的數組。
C.2 循環體內的對象聲明
注解
如果在循環體內聲明類變量,那么在循環的每次迭代時都需要構造并
析構該對象。這種構造并析構帶來的問題就是循環執行速度降低。
示例
for (i = 0; i < 1000; i++)
{
FOO a;
...
}
準則
在循環體外部聲明類類型變量,而避免在內部聲明。
D. 編寫只讀型代碼準則
D.1 ROM中的const對象
注解
通常,如果const對象具有如下一些屬性,則可以存儲在ROM中:
-- 具有static存儲時長
-- 由常量表達式初始化
-- 是一個POD(plain old data)類型對象
POD類型對象具有如下屬性:
-- 一個無屬性(scalar)類型(數字, 枚舉和指針)
-- 類/結構/聯合體中所有數據成員都是public訪問權限并且是
POD類型, 同時類中沒有用戶自定義的構造函數/析構函數,沒有基類和虛函數
-- 所有元素都是POD類型的數組
示例
static const char lang[] = "EC++";
class A {
int a;
public:
A();
~A();
};
const A x;
'lang'可能存儲于ROM中,而‘x’則不會。
準則
欲將對象存儲于ROM中,則將對象聲明為POD類型,并使用常量初始化該對象。
NOTE: The form of presentation used here, and several of the specific
guidelines, were inspired by the excellent book by Thomas Plum and
Dan Saks, 'C++ Programming Guidelines' (Plum Hall Inc., 1991).