C++ Primer類設計者的工具
1 C++類中有四個不可或缺的部分,那就是構造函數、拷貝構造函數、指派操作符和析構函數。如果類中沒有定義這些函數,那麼編譯器将為類自動生成這些函數。當然,你也可以通過private控制政策限定不使用拷貝構造函數和指派操作符。
其中,拷貝構造函數、指派操作符和析構函數總稱為拷貝控制(copy control)。
當類中有指針類資料成員時,一般都需要自已實作類的拷貝控制。通常有兩種處理政策:一是定義值型類,每個類保留一份指針指向的對象的拷貝;另一種更常用的政策是使用智能指針(smart pointer),其通用技術是采用引用計數(reference count)來實作共享指針指向的對象。
我們知道C++中變量初始化有兩種形式:直接初始化和拷貝初始化。直接初始化将初始化放在圓括号中,而拷貝初始化使用=符号。對于内置類型,這兩者基本上沒有差別。但對于類類型,兩種方式是有差別的:直接初始化直接調用與實參比對的構造函數;而拷貝初始化總是調用拷貝構造函數,具體而言,就是拷貝初始化首先使用指定構造函數建立一個臨時對象,然後用拷貝構造函數将那個臨時對象拷貝到正在建立的對象。
支援拷貝初始化主要是為了與C的用法相容。當情況允許時,可以允許編譯器跳過拷貝構造函數直接建立對象,但編譯器沒有義務這樣做。注:事實上大多數編譯器都跳過了拷貝構造函數,因為這完全可以跳過。
我們知道可以用表示容量的單個參數來初始化容器,容器的這種構造方式使用了預設構造函數和拷貝構造函數。例如:
vector svec(5);
編譯器首先使用string的預設構造函數建立一個臨時值來初始化svec,然後使用拷貝構造函數将臨時對象拷貝到svec的每個元素。示例代碼如下
#include
#include
using namespace std;
class Test {
public:
Test() {
cout<<”Contructor”<<endl;
}
Test(const Test& t) {
cout<<”copy Contructor”<<endl;
}
};
int main() {
vector tvec(5);
return 0;
}
輸出結果為:(MinGW 2.05和VC6.0)
Contructor
copy Contructor
copy Contructor
copy Contructor
copy Contructor
copy Contructor
對于元素為類類型的數組,可以使用數組初始化清單來提供顯示元素初始化。此時,使用拷貝初始化來初始化每個元素。根據指定值建立适當類型的元素,然後用拷貝構造函數将該值拷貝到相應元素。當然,同前面一樣,是否跳過拷貝構造函數取決于編譯器(事實上,大多數編譯器跳過了這步)。
拷貝構造函數的形參是一個類類型引用(否則,參數本身就需要過拷貝構造函數了),但一般情況下,我們使用const修飾。并且,一般不應該設定為explicit。有時需要禁止拷貝類,例如,iostream類就不允許拷貝。這時,應當顯示聲明拷貝構造函數為private,此時可以不定義該函數。若聲明為private且進行了函數定義,則類的友元和成員仍然可以進行拷貝。
合成的拷貝構造函數:
如果我們沒有定義拷貝構造函數,則編譯器會自動生成一個,把這個自動生成的拷貝構造函數叫合成的拷貝構造函數(Synthesized Copy Constructor)。合成的拷貝構造函數執行逐個成員初始化,将新對象初始化為原對象的副本。如果成員是内置類型,則執行位拷貝;如果成員是類類型,則調用相應的拷貝構造函數;如果成員是數組類型,則分别對每個數組元素進制拷貝。
2 标準庫容器、string和shared_ptr類支援移動也支援拷貝,IO類和unique_ptr類可以移動但不能拷貝。
3 int i=42;
Int &r=i;
Int &&rr=I; //錯誤不能将一個右值引用綁定到一個左值上
Int &r2=I42; //錯誤,I42是一個右值
Const int &r3=I42;//錯誤,可以将const引用綁定到一個右值上
Int &&rr2=I42; 将rr2綁定到乘法結果上
雖然不能将一個右值引用直接綁定到一個左值上,但我們可以顯示将一個左值轉換對應右值引用類型。可以調用一個名為move的新标準庫函數來獲得綁定到左值上的右值引用。
Int &&rr3=std::move(rr2); 在調用move之後,我們不能對移後原對象的值做任何假設。
4 區分移動和拷貝的重載函數通常有一個版本接受一個const T&,而另一個版本接受一個T&&。
5 引用限定符:
class Foo{
Public:
Foo &operator=(const Foo&) &; //隻能向可修改的左值指派
};
Foo &Foo::operator=(const Foo &rhs) &
{
Return *this;
}
6 成員通路運算符:
Class StrBlobPtr{
Std::string & operator() const{
Auto p=check(curr, “dereference past end”);
Return (p)[curr];
}
Std::string operator->() const{
Return & this->operator();
}
}
7 重載的函數與function
我們不能(直接)将重載函數的名字存入function類型的對象中;
Int add(int i,int j){ return i+j;}
Sales_data add(const Sales_data &, const Sales_data&);
Map<string,function<int(int,int)>> binops;
Binops.insert({“+”,add}); //錯誤,不知是哪個add
Int (*fp)(int,int)=add;
Binops.insert({“+”,fp});//正确,fp指向一個正确的add版本
8 C++11新标準提供一種防止繼承發生的方法,在類名跟一個關鍵詞final
9 如果派生類(既内層作用域)的成員與基類(既外層作用域)的某個成員同名,派生類将在作用域内隐藏該基類成員。即使派生類成員和基類成員的形參清單不一緻,基類成員也仍然被隐藏;
Struct Base{
Int menfcn();
}
Struct Derived:Base{
Int memfcn(int);
}
Derived d;Base b;
b.memfcn();
d.memfcn(10);
d.memfcn(); //錯誤,參數清單為空的memfcn被隐藏了
d.Base::memfcn(); //正确,調用Base::memfcn
10 不存在從基類向派生類的隐式類型轉換:
Quote base;
Bulk_quote* bulkP=&base; //錯誤,不能将基類轉換成派生類
Bull_quote& bulkRef=base; //錯誤,不能将基類轉換成派生類
11 繼承關系類型之間的轉換規則:
從派生類向基類的類型轉換隻對指針或引用類型有效;
基類向派生類不存在隐式類型轉換;
和任何其他成員一樣,派生類向基類的類型轉換也可能會由于通路受限而變得不可行;
12 如果派生類的成員與基類的某個成員同名,則派生類将在其作用域内隐藏基類成員。即使派生類成員和基類成員的形參清單不一緻,基類成員仍然會被隐藏掉;
Struct Base{
Int memfcn();
}
Struct Derived:Base{
Int memfcn(int);
}
Derived d;Base b;
b.memfcn();
d.memfcn(10);
d.menfcn(); //錯誤,參數清單為空的memfcn被隐藏了
d.Base::memfcn();
13 函數模闆:template
Int compare(const T &v1,const T &v2){
}
Template T foo(T p){
T tmp=p;
Return tmp;
}