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