1、避免編寫拷貝構造函數和賦值操作符,如果默認版本並不適用,可以考慮把拷貝構造函數和賦值操作符聲明為私有,禁止類實例的復制 2、避免在析構函數中編寫代碼 需要析構函數的原因可能有好幾個: a、在基類中,可能需要聲明虛擬析構函數,這樣就可以使用一個指向基類的指針指向一個派生類的實例 b、在派生類中,並不需要把析構函數聲明為虛擬函數,但是為了增加可讀性,也可以這樣做 c、可能需要聲明析構函數並不拋出任何異常 下來討論一下為什麼析構函數應該是空的: [cpp] class Student { public: Student { if (!number_) { number_ = new int(age); } } ~Student() { if (number_) { delete number_; } } private: int* number_; }; 看看這個Student類,因為他在構造函數中獲取了一些資源,所以需要在析構函數中釋放掉。 但是現在問題來了,這個類的設計----每次添加一個表示個人描述的新元素(例如number_)時,都需要在析構函數中添加對應的清理代碼,這就違背了“不要迫使程序員記住某些事情”的原則;另一個問題是,缺少安全檢查,首先你的number_可能是要必須大於0的,這樣還得在new的前面進行if判斷,另外一個極端的情況: [cpp] #include "stdafx.h" #include "iostream" #include "string" class A { public: A() { std::cout << "Creating A" << std::endl; } ~A(){ std::cout << "Destroying A" << std::endl; } }; class B { public: B() { std::cout << "Creating B" << std::endl; } ~B(){ std::cout << "Destroying B" << std::endl; } }; class C : public A { public: C() { std::cout << "Creating C" << std::endl; throw "Don't like C"; } ~C(){ std::cout << "Destroying C" << std::endl; } private: B b_; }; int _tmain(int argc, _TCHAR* argv[]) { try { C c; } catch (...) { std::cout << "Caught an exception" << std::endl; } return 0; } 因為在C中throw了一個異常,所以導致class C的析構函數沒有被執行,這就導致了一些問題的產生 改成下面的形式設計就要好得多: [cpp] class Student { public: Student(const int number, const char* name) { SCPP_ASSERT(number > 0, "number must be large 0"); number_ = new int(number); SCPP_ASSERT(name != NULL, "name must be not null"); name_ = new std::string(name); } ~Student() { } private: Student(const Student& student); Student& operator = (const Student&); scpp::ScopedPtr<int> number_; scpp::ScopedPtr<std::string> name_; }; 即使第二個安全檢查拋出異常,執行name_的智能指針的析構函數仍然會被調用,並執行他的清理工作。另一個附帶的好處是,我們並不需要操心把這些智能指針初始化為NULL,這是自動完成的。因此,可以看到構造函數拋出異常是一種潛在的危險行為;對應的析構函數將不會被調用,因此可能會存在問題,除非析構函數是空函數。 3、編寫一致的比較操作符 常用的操作符有< > <= >= == !=,站在C++的角度,這些操作可以寫成6個相互完全獨立的函數,但是他們相互之間又有聯系,比如x1>x2成立,x2<x1和x1>=x2也是成立的。所以,我們需要一些步驟來幫我實現這個目標(往之前的scpp_types.h上面添加): [cpp] #ifndef __SCCP_TYPES_H__ #define __SCCP_TYPES_H__ #include <ostream> #include "scpp_assert.h" template <typename T> class TNumber { public: TNumber(const T& x =0 ) :data_(x) { } operator T() const { return data_; } TNumber &operator = (const T& x) { data_ = x; return *this; } TNumber operator ++(int) { TNumber<T> copy(*this); ++data_; return copy; } TNumber operator ++() { ++data_; return *this; } TNumber& operator += (T x) { data_ += x; return *this; } TNumber& operator -= (T x) { data_ -= x; return *this; } TNumber& operator *= (T x) { data_ *= x; return *this; } TNumber& operator /= (T x) { SCPP_ASSERT(x != 0, "Attempt to divide by 0"); data_ /= x; return *this; } T operator / (T x) { SCPP_ASSERT(x != 0, "Attempt to divide by 0"); return data_ / x; } private: T data_; }; typedef long long int64; typedef unsigned long long unsigned64; typedef TNumber<int> Int; typedef TNumber<unsigned> Unsigned; typedef TNumber<int64> Int64; typedef TNumber<unsigned64> Unsigned64; typedef TNumber<float> Float; typedef TNumber<double> Double; typedef TNumber<char> Char; class Bool { public: Bool(bool x = false) :data_(x) { } operator bool () const { return data_; } Bool& operator = (bool x) { data_ = x; return *this; } Bool& operator &= (bool x) { data_ &= x; return *this; } Bool& operator |= (bool x) { data_ |= x; return *this; } private: bool data_; }; inline std::ostream& operator << (std::ostream& os, Bool b) { if (b) { os << "True"; } else { os << "False"; } return os; } #define SCPP_DEFINE_COMPARISON_OPERATORS(Class) \ bool operator < (const Class& that) const { return CompareTo(that) < 0; } \ bool operator > (const Class& that) const { return CompareTo(that) > 0; } \ bool operator ==(const Class& that) const { return CompareTo(that) ==0; } \ bool operator <=(const Class& that) const { return CompareTo(that) <=0; } \ bool operator >=(const Class& that) const { return CompareTo(that) >=0; } \ bool operator !=(const Class& that) const { return CompareTo(that) !=0; } #endif 測試代碼(vs2012+win7環境): [cpp] #include "stdafx.h" #include "scpp_assert.h" #include "iostream" #include "scpp_vector.h" #include "scpp_array.h" #include "scpp_matrix.h" #include "algorithm" #include "scpp_types.h" #include "scpp_refcountptr.h" #include "scpp_scopedptr.h" #include "scpp_ptr.h" #include "string" #define STUDENTAGE 10 class Student { public: Student(int age) { SCPP_ASSERT(age > 0, "number must be large 0"); age_ = age; } int CompareTo(const Student& that) const { if (this->age_ > that.age_) { return 1; } else if(this->age_ == that.age_) { return 0; } else { return -1; } } SCPP_DEFINE_COMPARISON_OPERATORS(Student); private: int age_; }; int _tmain(int argc, _TCHAR* argv[]) { Student studentFirst(STUDENTAGE); Student studentSecond(STUDENTAGE); if (studentFirst >= studentSecond) { std::cout << "(studentFirst > studentSecond) && (studentFirst == studentSecond)" << std::endl; } return 0; } 4、使用標准C函數庫的錯誤 C函數庫中,在好幾個方面都是不安全的,還可能導致程序崩潰 比如使用stdio.h中想sprintf()這樣的函數可能會有下面的問題: a、有些函數接受字符數組的指針(char *),如果向他們傳遞一個NULL而不是指向合法的C字符串的指針,將會崩潰(比如strlen(NULL);就可以秒掉程序) b、有些函數將寫入一個緩沖區,他們可能會改寫越過緩沖區尾部的內存,從而導致不可預料的程序行為 ..... 上面的問題,可以這樣處理: a、提供執行所有必要安全檢查的版本,並按照與處理空字符串的相同方式處理NULL指針 b、對於不能犧牲字符串操作速度的應用程序,提供只在測試時才激活的臨時安全檢查的函數版本 ...... 但是,這些問題最好的解決方案是不用C字符串函數庫,而是改用C++所提供的類,比如: strlen(name)可以改用name.size(),對於strcpy()可以僅僅 使用字符串賦值操作符;strcat()或strncat()可以改用: [cpp] int _tmain(int argc, _TCHAR* argv[]) { std::ostringstream buffer; buffer << "zeng"; buffer << "raoli"; std::string result = buffer.str(); std::cout << "this result string is : " << result << std::endl; return 0; } 或者采用更簡短的形式: [cpp] int _tmain(int argc, _TCHAR* argv[]) { std::string result = "zeng"; result += "raoli"; std::cout << "this result string is : " << result << std::endl; return 0; } 這些代碼不僅更容易閱讀,並且更安全,而且對於較長的字符串,他們的速度也快於strcat()!因為不存在需要分配和改寫緩沖區