對於一個類來說,我們把copy constructor、copy-assignment operator、move constructor、move-assignment operator、destructor統稱為copy control。
今天我們先來聊聊其中的copy constructor、copy-assignment operator的destructor這三個。
copy constructor
copy constructor:一個constructor如果他的第一個參數是對類的引用,且其他的參數都有缺省值(default values)則,這是一個copy constructor。
1,第一個參數必須是引用類型,因為當我們把一個object當做參數傳遞給一個方法的非引用變量的時候會自動調用copy constructor方法,如果copy constructor自身的參數就是非引用類型的話,這個方法就會引起無限遞歸調用,然後你的程序就boomshakalaka~~。
2,一般我們會把第一個參數設成const,因為一般情況下不會對其進行修改,除非你另有打算。
3,因為copy constructor在很多情況下是默認調用的,如以下情況,所以一般不會把copy constructor設成explicit。
1 std::string s; 2 std::string s1 = s; //隱式調用了copy constructor 3 std::string s2 = std::string(s1);//顯式調用了copy constructor
1 class Foo{ 2 public: 3 Foo(const Foo&); 4 //... 5 };
何時發生copy constructor調用
為了弄清這個問題我們需要弄清另外一組概念:direct initialization 和copy initialization。
direct initialization:要求編譯器按照一般的方法匹配(function matching)來選擇要調用的方法。
copy initialization:要求編譯器將右操作數拷貝到左操作數,有必要的話還會進行類型轉換,這個過程會調用copy constructor或者move constructor(本文暫不介紹)。
1 std::string s1("balabala"); //direct initialization 2 std::string s2(10, 'a'); //direct initialization 3 std::string s3 = s2; //copy initialization 4 std::string s4 = std::string(s3); //copy initialization 5 std::string s5 = "const char* converts to string";//copy initialization
copy initialization發生的情況如下:
1,用=來初始化定義的變量時。
2,把一個object當做參數傳遞給一個方法的非引用變量的時。
3,方法返回一個非引用類型的object時。(返回時會首先生成一個臨時object)
4,用花括號列表初始化一個數組或聚合類(aggregate class)成員時。
由於編譯器帶來的誤解:
現在的編譯器有時候會自動繞過copy constructor即編譯器會把下面這一句話
std::Book book = "9-9-9-9";//假設Book是一個自定義的類
換成下面這個
std::Book book("9-9-9-9");
請注意在執行上上面兩句語言是完全不一樣的,第一句會首先調用Book(const char*)構造函數生成一個臨時object然後再調用Book(const Book&)把臨時object復制給book。而第二句話會直接調用Book(const char*)然後完事兒。如果你想驗證他們的區別可以實現Book類並將Book(const Book&)設置成私有方法(防止編譯器自動優化),之後你就會發現第一條語句無法執行了。
Copy-assignment operator
copy-assignment operator:寫這個方法就是對=操作符進行重載。
1,copy-assignment operator的返回值一般是對其左操作數(left-hand operand)的引用,這是為了讓object的行為更像內置類型而決定的。
1 class Foo{ 2 public: 3 Foo& operator=(const Foo&); 4 //... 5 };
何時發生copy-assignment operator調用
答案很顯然是用到=操作符的時候啊,但是這裡要注意的是
初始化的時候並不會調用copy-assignment operator
初始化的時候並不會調用copy-assignment operator
初始化的時候並不會調用copy-assignment operator
重要的事情說三遍,舉例如下
1 std::string s; 2 std::string s1 = s; //對s1進行初始化,調用的是copy constructor 3 s1 = s; //對s1進行賦值,調用的是copy-assignment operator
Destructor
Destructor:destructor有兩個部分,function body和destruction part,前者由類的編寫者寫明需要做的內容,後者是隱式的,不需要程序員關心,在function body執行完後自動執行,會銷毀類的非靜態數據成員。
1,因為Destructor沒有參數,所以它是不能被重載的
1 class Foo{ 2 public: 3 ~Foo(); 4 //... 5 };
何時發生Destructor調用
1,當超出object 的作用域(scope)時。
2,容器銷毀時(container),裡面的元素(element)也會跟著調用自身的destructor從而銷毀。
3,人為使用delete的時候。
4,由某個表達式創建的臨時變量在這個表達式執行完後將自動調用destructor從而銷毀。
5,類的成員如果自身有destructor,會在這個類銷毀的時候調用自身的destructor。
關於編譯器自動提供的版本(Synthesized)
Synthesized copy constructor:即使我們提供了其他版本的copy constructor,編譯器仍然會提供這個版本的copy constructor給我們,它會依次復制非靜態成員給被創建的object,對數組也能正常工作,對於class類型會調用它們自己的copy constructor。
Synthesized copy-assignment operator:行為和Synthesized copy constructor類似,依次把非靜態成員復制給左操作數。
Synthesized destructor:destructor的function body為空。
關於何時我們需要自定義上述的三個方法
1,當需要destructor時,上述三給方法都是需要的。
2,當需要copy constructor時,copy-assignment operator也是需要的,反之亦然。
而當我們需要刪除自己動態分配的內存時,就要用到destructor。
當我們需要進行深度復制時會用到另外兩個,比如對指針指向的元素進行復制等等。
關於delete和default的用法
我們可以用default顯示聲明我們想要用默認版本的copy control,也可以用delete顯示聲明我們完全不需要這類方法來達到禁止這個object進行相關的復制和賦值操作。
1,我們能delete除了destructor以外的所有方法來達到顯示告知這個object不能進行相關操作的目的,delete只能寫在一次聲明出現的地方。
2,我們能對所有有默認版本的函數用default顯示聲明我們需要這個默認版本,default可以寫在方法聲明的地方也可以寫在方法定義的地方。
1 class Foo{ 2 public: 3 Foo() = default; //顯式說明使用默認版本 4 Foo(const Foo&) = delete; //delete copy constructor 5 Foo& operator=(const Foo&) = delete; //delete copy-assignment operator 6 ~Foo() = default; //顯式說明使用默認版本 7 void myFuntion() = delete; //delete自己的方法 8 //... 9 };