26.當心潛在的二義性。
一些潛在的二義性的例子:
~~~
class A{
public:
A(const B&);
};
class B{
public:
operator A() const;
};
void f(const A&);
~~~
一般情況下,這樣寫不會出錯,但當調用f函數傳入一個 B的對象b時,就會發生二義性錯誤,b既可以通過A的構造函數獲得一個A的對象,也可以通過B的類型轉換運算符來將b變成一個A的對象再使用,而編譯器不知道應該使用哪種方法。這是一個潛在的二義性,其一般情況下正常無誤。
還有更簡單一點的二義性,當兩個函數重載時,如 f(int ) f(char);,而傳入一個double類型的參數d時,就會發生二義性,編譯器不知道應該將d轉換為char類型還是int類型。
多繼承中充滿了二義性的可能。如多個基類具有同名的函數或數據成員,就必須指明成員的基類來消除二義性,即使一些成員在其所在的基類中是私有的,即派生類無法訪問,但這還是有二義性的錯誤。為什么消除 對類成員的引用產生的二義性時不考慮訪問權限:改變類成員的訪問權限不應該改變程序的含義,即當類定義好之后,改變成員的訪問屬性會導致一些訪問出錯或這依舊正常,這是正確的,而如果改變訪問屬性改變了程序的含義(如A 類B類派生 出C類,c中一個函數調用兩個基類中同名的成員,如果 消除二義性時考慮了訪問權限, 當A中成員為公有,b中成員為私有時,c中函數調用a中成員,而改變了訪問權限,a中為私有,b中為共有,就會導致c中函數調用b中成員,改變了程序的含義,但是對于程序猿,卻一無所知。如果保護考慮的話,會出現訪問出錯,會提示程序員進行修改) 所以最好還是顯示的消除二義性。
27.如果不想使用隱式生成的函數就要顯式的禁止它。
對于這些不想使用的函數禁止它,原因很簡單,為了之后更容易更新和維護,當你現在在寫這個類時,你沒有禁止這些不應該使用的函數,你記得這些事情,所以你自己不會犯這些錯誤,但當過了很長一段時間,或者其他人接替了你的工作來對你的代碼進行升級和維護時,這個沒有禁止的功能就很有可能出現在一些地方讓你措手不及。所以最好還是禁止這些不應該使用的函數。
對于類中一些函數,編譯器會隱式的自動生成,如缺省的無參構造函數,賦值 =,復制構造函數等,要禁止這些函數,一個簡單的方法就是聲明這些函數為private ,顯示聲明防止編譯器自動生成,而private防止其他人調用。
但這樣還是不安全,成員函數和 友元函數還是可以調用這些私有函數。這樣就只去聲明這些函數,而不去定義函數體,當其他函數調用時,編譯器就會在鏈接時報錯。
28.劃分全局名字空間。
使用命名空間來解決命名沖突。對命名空間之前有過一些研究:[c++命名空間](http://blog.csdn.net/luo_xianming/article/details/22818957),命名空間是可以嵌套的。
命名空間的使用方式有三種, using namespace xxx;之后所有的xxx命名空間的成員都可以直接訪問。 using xxx::成員 ,對于xxx命名空間中的這個成員可以直接使用。xxx::成員,在一個地方使用xxx命名空間中的這個成員。
可以使用struct來近似實現namespace , 先創建一個結構用以保存全局符號名,然后將這些全局符號名作為靜態成員放入結構中。訪問時加上struct的名作為前綴 ,與命名空間類似。
~~~
struct sdm{
static const double VERSION ;
class A{
public:
void f(){cout<<"SDM"<<endl;}
};
};
const double sdm::VERSION = 1.0;
class A{
public:
void f(){cout<<"GLOBEL"<<endl;}
};
int main(){
cout<<sdm::VERSION;
sdm::A sdma;
A a;
~~~
對于類型名,可以顯式地去掉空間引用:
~~~
typedef sdm::A A;
~~~
對于函數,一般返回一個函數的常指針:
~~~
sdm::A& (* const getA)()= sdm::getA;
sdm::A& (& getA)() = sdm::getA;//返回函數的引用
~~~