Item 1: View C++ as a federation of languages.
視C++為一種聯(lián)邦語(yǔ)言(federation ofrelated languages)。
也就是四種子語(yǔ)言的組合,每一個(gè)種類(lèi)有它自己的特性:C語(yǔ)言:、語(yǔ)句、預(yù)處理程序、內(nèi)置數(shù)據(jù)類(lèi)型、數(shù)組、指針等都來(lái)自C.
面向?qū)ο驝++:構(gòu)造函數(shù)和析構(gòu)函數(shù)、類(lèi)、多態(tài)、繼承等。
模板C++:元編程,它的約定(conventions)一般不影響其他規(guī)則。
STL:模板庫(kù)。
Item 2: Prefer consts, enums, and inlines to #defines.
可以說(shuō)是比起預(yù)處理器(preprocessor),更喜歡編譯器(compiler)。
- 選擇
const定義常量而不是用#define:
const double AspectRatio = 1.653;
常量定義通常放在頭文件中,定義常量指針:
const std::string authorName("Scott Meyers");
要將常量的范圍限制為類(lèi),必須使它成為成員,并且為了確保最多有一個(gè)常量的副本,您必須使它成為靜態(tài)成員:
class GamePlayer {
private:
static const int NumTurns = 5; // constant declaration
int scores[NumTurns]; // use of constant
...
};
上面看到的是NumTurns的聲明,而不是定義;
c++要求為所使用的任何東西提供一個(gè)定義,但是特定于類(lèi)的常量是靜態(tài)且是整型的(例如,整數(shù)、char、bools)時(shí)候是一個(gè)例外。
As long as you don’t take their address, you can declare them and use them without providing a definition。
但是當(dāng)去地址的時(shí)候,可以定義為:
const int GamePlayer::NumTurns; // definition of NumTurns;
將其放入實(shí)現(xiàn)文件中,而不是頭文件中。
沒(méi)有初始值,因?yàn)轭?lèi)常量的初值是在聲明常量的地方提供的,所以在定義點(diǎn)不允許在設(shè)置初始值。
類(lèi)內(nèi)初始化只允許用于整數(shù)類(lèi)型和常量。在不能使用上述語(yǔ)法的情況下,可以將初始值放在定義的地方:
class CostEstimate {
private:
static const double FudgeFactor; // declaration of static class
... // constant; goes in header file
};
const double // definition of static class
CostEstimate::FudgeFactor = 1.35; // constant; goes in impl. file
- 如果編譯器堅(jiān)持在編譯期間知道數(shù)組的大小,則可以用
enum技術(shù):
class GamePlayer {
private:
enum { NumTurns = 5 }; // “the enum hack” — makes
// NumTurns a symbolic name for 5
int scores[NumTurns]; // fine
...
};
- 關(guān)于枚舉
enum:取const的地址是合法的,但取enum的地址是不合法的,通常取#define的地址也是不合法的。如果你不想讓人們得到一個(gè)指針或者引用,enum是一個(gè)很好的方法來(lái)實(shí)現(xiàn)這個(gè)約束。
- 通過(guò)使用內(nèi)聯(lián)函數(shù)的模板,您可以獲得宏的所有效率,以及常規(guī)函數(shù)的所有可預(yù)測(cè)行為和類(lèi)型安全性:
template<typename T> // because we don’t
inline void callWithMax(const T& a, const T& b) // know what T is, we pass by reference-to const
{
f(a > b ? a : b);
}
- Things to Remember
? For simple constants, prefer const objects or enums to #defines.
? For function-like macros, prefer inline functions to #defines
Item 3: Use const whenever possible.
如果單詞
const出現(xiàn)在星號(hào)的左邊,所指向的是常量;如果單詞const出現(xiàn)在星號(hào)的右邊,指針本身就是常量;const放在類(lèi)型前或者后是同樣的聲明:
void f1(const Widget *pw); // f1 takes a pointer to a
// constant Widget object
void f2(Widget const *pw); // so does f2
- STL迭代器
iterator是基于指針建模的,因此迭代器的行為非常類(lèi)似于T *指針。聲明const迭代器類(lèi)似于聲明const指針:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin(); // iter acts like a T* const
*iter = 10; // OK, changes what iter points to
++iter; // error! iter is const
std::vector<int>::const_iterator cIter = vec.begin(); // cIter acts like a const T*
*cIter = 10; // error! *cIter is const
++cIter; // fine, changes cIter
- 函數(shù)返回一個(gè)
const:讓函數(shù)返回一個(gè)常數(shù)值通常是不合適的,但有時(shí)這樣做可以在不放棄安全性或效率的情況下降低客戶(hù)機(jī)錯(cuò)誤的發(fā)生率, 除非需要修改參數(shù)或本地對(duì)象,否則請(qǐng)確保聲明它為const。
class Rational { ... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
Rational a, b, c;
//防止出現(xiàn)這樣的錯(cuò)誤
if (a * b = c) ... // oops, meant to do a comparison!
-
constMember Functions
兩個(gè)原因:
1.哪些成員函數(shù)可以修改const成員對(duì)象;
- 提高c++程序性能的基本方法之一是通過(guò)
reference-to-const來(lái)傳遞對(duì)象;
- 成員函數(shù)之間只有一致性
constness不同,可以重載這種const性質(zhì):
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[] for
{ return text[position]; } // const objects
char& operator[](std::size_t position) // operator[] for
{ return text[position]; } // non-const objects
private:
std::string text;
};
TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const TextBlock::operator[]
std::cout << tb[0]; // fine — reading a non-const TextBlock
tb[0] = ’x’; // fine — writing a non-const TextBlock
std::cout << ctb[0]; // fine — reading a const TextBlock
ctb[0] = ’x’; // error! — writing a const TextBlock
上面的成員函數(shù)定義后面加 const 是說(shuō)明this指針是一個(gè)pointer to const object,所以函數(shù)里不能修改non-static data member的值,注意,static member是可以被修改的。其中 tb[0] = ’x’是傳值,而且不應(yīng)該修改成員函數(shù)返回類(lèi)型。
- bitwise constness (also known as physical constness) and logical constness:
其中 bitwise constness表示const函數(shù)不會(huì)修改objects內(nèi)的任何bits,這也是c++對(duì)constness的定義,const成員函數(shù)不允許修改non-static data member。但是成員函數(shù)在有的時(shí)候不是完全的bitwise constness,特別是該成員函數(shù)改變一個(gè)指針指向什么的時(shí)候。只要該指針在這個(gè)object內(nèi)。
例子如下:
class CTextBlock {
public:
...
char& operator[](std::size_t position) const // inappropriate (but bitwise const) declaration of operator[]
{ return pText[position]; }
private:
char *pText;
};
為什么,因?yàn)樵摵瘮?shù)返回對(duì)對(duì)象內(nèi)部數(shù)據(jù)的引用(returns a reference to the object’s internal data)。
const CTextBlock cctb("Hello"); // declare constant object
char *pc = &cctb[0]; // call the const operator[] to get a pointer to cctb’s data
*pc = ’J’; // cctb now has the value “Jello”
bitwise cosntness成員函數(shù)改變了對(duì)象成員的值,因此引出logical constness:認(rèn)為const成員函數(shù)可能會(huì)修改調(diào)用它的對(duì)象中的一些位,但只是以客戶(hù)機(jī)無(wú)法檢測(cè)到的方式。
比如這個(gè)例子, 需要記錄當(dāng)前長(zhǎng)度是否合法、計(jì)算文本塊的長(zhǎng)度,:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // last calculated length of textblock
bool lengthIsValid; // whether length is currently valid
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // error! can’t assign to textLength and lengthIsValid in a const
lengthIsValid = true;
} // member function
return textLength;
}
雖然textLength 和 lengthIsValid 聲明不是const,是可修改的,但是常量成員函數(shù)并不能作用在這兩個(gè)成員數(shù)據(jù)上,解決的方法是聲明為mutable:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; // these data members may
mutable bool lengthIsValid; // always be modified, even in
}; // const member functions
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // now fine
lengthIsValid = true; // also fine
}
return textLength;
}
- Avoiding Duplication in const and Non-const Member Functions
避免常量成員函數(shù)和非常量成員函數(shù)冗余,在非常量成員函數(shù)里調(diào)用常量版本:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // same as before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) // now just calls const op[]
{
return
const_cast<char&>( // cast away const on
// op[]’s return type;
static_cast<const TextBlock&>(*this) // add const to *this’s type;
[position] // call const version of op[]
);
}
...
};
如果你從一個(gè)const函數(shù)調(diào)用一個(gè)非const函數(shù),你會(huì)冒著你承諾不修改的對(duì)象會(huì)被修改的風(fēng)險(xiǎn)。這就是為什么const成員函數(shù)調(diào)用非const成員函數(shù)是錯(cuò)誤的:對(duì)象可以更改。這就是static_cast在這種情況下處理*this的原因:沒(méi)有與常量相關(guān)的危險(xiǎn)。
Things to Remember
? Declaring something const helps compilers detect usage errors. const can be applied to objects at any scope, to function parameters and return types, and to member functions as a whole.
? Compilers enforce bitwise constness, but you should program using logical constness.
? When const and non-const member functions have essentially identical implementations, code duplication can be avoided by having the non-const version call the const version.
Item 4: Make sure that objects are initialized before they’re used.
c++規(guī)則規(guī)定,對(duì)象的數(shù)據(jù)成員在輸入構(gòu)造函數(shù)體之前初始化,因此用成員初始化列表,初始化列表中的參數(shù)用作各數(shù)據(jù)成員的構(gòu)造函數(shù)(復(fù)制構(gòu)造、)參數(shù):
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name), // theName is copy-constructed from name
theAddress(address), // these are now all initializations
thePhones(phones),
numTimesConsulted(0)
{} // the ctor body is now empty
對(duì)于大多數(shù)類(lèi)型,對(duì)復(fù)制構(gòu)造函數(shù)的單個(gè)調(diào)用要比對(duì)默認(rèn)構(gòu)造函數(shù)的調(diào)用以及隨后對(duì)復(fù)制賦值操作符的調(diào)用更有效——有時(shí)甚至更有效。
通常最好通過(guò)成員初始化來(lái)初始化所有內(nèi)容。 類(lèi)似地,即使希望默認(rèn)構(gòu)造數(shù)據(jù)成員,也可以使用成員初始化列表;只需要不指定任何初始化參數(shù),因?yàn)楫?dāng)用戶(hù)定義類(lèi)型的數(shù)據(jù)成員在成員初始化列表中沒(méi)有初始化器時(shí),編譯器會(huì)自動(dòng)調(diào)用這些數(shù)據(jù)成員的默認(rèn)構(gòu)造函數(shù):
ABEntry::ABEntry()
: theName(), // call theName’s default ctor;
theAddress(), // do the same for theAddress;
thePhones(), // and for thePhones;
numTimesConsulted(0) // but explicitly initialize
{}
必須初始化const或引用的數(shù)據(jù)成員;他們不能被分配。
初始化順序:基類(lèi)在派生類(lèi)之前初始化(參見(jiàn)第12項(xiàng)),并且在類(lèi)中,數(shù)據(jù)成員按照聲明它們的順序初始化。
靜態(tài)對(duì)象:函數(shù)內(nèi)部的靜態(tài)對(duì)象稱(chēng)為局部靜態(tài)對(duì)象(因?yàn)樗鼈兪呛瘮?shù)的局部對(duì)象),其他類(lèi)型的靜態(tài)對(duì)象稱(chēng)為非局部靜態(tài)對(duì)象;
翻譯單元是產(chǎn)生單個(gè)目標(biāo)文件的源代碼,它基本上是一個(gè)單一的源文件,加上它所有的#include文件。我們所關(guān)心的問(wèn)題至少涉及兩個(gè)單獨(dú)編譯的源文件,每個(gè)文件至少包含一個(gè)非本地靜態(tài)對(duì)象(即,一個(gè)全局對(duì)象,在名稱(chēng)空間范圍內(nèi),或類(lèi)或文件范圍內(nèi)的靜態(tài)對(duì)象)?,F(xiàn)在問(wèn)題是:如果一個(gè)翻譯單元中的非本地靜態(tài)對(duì)象的初始化使用另一個(gè)翻譯單元中的非本地靜態(tài)對(duì)象,那么它使用的對(duì)象可以未初始化,因?yàn)樵诓煌g單元中定義的非本地靜態(tài)對(duì)象的初始化的相對(duì)順序是未定義的。
class FileSystem { // from your library’s header file
public:
...
std::size_t numDisks() const; // one of many member functions
...
};
extern FileSystem tfs; // declare object for clients to use
// (“tfs” = “the file system” ); definition
// is in some .cpp file in your library
class Directory { // created by library client
public:
Directory( params );
...
};
Directory::Directory( params )
{
...
std::size_t disks = tfs.numDisks(); // use the tfs object
...
}
解決辦法:?jiǎn)卫J剑⊿ingleton pattern):所要做的就是將每個(gè)非本地靜態(tài)對(duì)象移動(dòng)到它自己的函數(shù)中,在那里它被聲明為靜態(tài)。這些函數(shù)返回對(duì)它們包含的對(duì)象的引用。然后客戶(hù)機(jī)調(diào)用函數(shù)而不是引用對(duì)象。換句話(huà)說(shuō),非本地靜態(tài)對(duì)象被替換為本地靜態(tài)對(duì)象。
class FileSystem { ... }; // as before
FileSystem& tfs() // this replaces the tfs object; it could be
{ // static in the FileSystem class
static FileSystem fs; // define and initialize a local static object
return fs; // return a reference to it
}
class Directory { ... }; // as before
Directory::Directory( params ) // as before, except references to tfs are
{ // now to tfs()
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir() // this replaces the tempDir object; it
{ // could be static in the Directory class
static Directory td( params ); // define/initialize local static object
return td; // return reference to it
}
Things to Remember
? Manually initialize objects of built-in type, because C++ only sometimes initializes them itself.
? In a constructor, prefer use of the member initialization list to assignment inside the body of the constructor. List data members in the initialization list in the same order they’re declared in the class.
? Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.