淺談類的幾個基礎建構函式
通過例子來介紹下C++類的幾個基礎建構函式。
我們以一個C型別的字串為例:
class myString { public: myString(const char* rhs = 0); // 預設(含參)建構函式 myString(const myString& rhs); // 拷貝建構函式 myString(myString&& rhs) noexcept; // 移動建構函式 myString& operator=(const myString& rhs); // 拷貝賦值函式 myString& operator=(myString&&) noexcept; // 移動賦值函式 ~myString(); // 解構函式 private: char* m_data; };
(一)、我們定義一個myString類,僅包含一個char* 的指標。先來看看它的預設建構函式:
inline myString::myString(constchar* rhs) { if (rhs) { m_data = (char*)new char[strlen(rhs) + 1]; strcpy_s(m_data,strlen(rhs)+1, rhs); } else { m_data = new char[1]; *m_data = '\0'; } }
這裡僅是申請了一塊記憶體,對傳入字串進行了拷貝。
(二)、關於拷貝建構函式。拷貝建構函式是僅是對於傳入物件的一次深拷貝。記得使用引用傳入,由於我們不需要對傳入物件進行修改操作,那就對它宣告為const吧。
inline myString::myString(const myString& rhs) { m_data = (char*)new char[strlen(rhs.m_data) + 1]; strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data); }
(三)、對於拷貝賦值函式我們尤其要注意自我賦值問題。如果我們不進行自我賦值檢測,即傳入物件和被賦值物件是同一個的話,當delete完之後,傳入的物件也已經不存在了,這並不是我們想要的結果。
inline myString& myString::operator=(const myString& rhs) { if (this != &rhs) { if (m_data) delete m_data; m_data = (char*)new char[strlen(rhs.m_data) + 1]; strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data); }
return *this; }
(四)、關於移動建構函式。移動建構函式給我帶來一種 “ 偷 ” 的概念。如何理解呢?我們來列舉2個移動建構函式的主要應用場景:1. 假設我們需要將一批myString物件存入vector,當vector由於原容量不夠大而發生擴充時,之前的C++版本中vector內部會重新申請一塊記憶體,然後把之前儲存的物件一個一個拷貝到新記憶體上,並且釋放原記憶體。
當C++11以後我們可以藉助移動建構函式這個“偷”的概念。怎麼偷? 先看下程式碼:
inline myString::myString(myString&& rhs) noexcept : m_data(rhs.m_data) { rhs.m_data = NULL; }
這不就是指標的拷貝,換言之淺拷貝嗎? 可以這麼說!既然原先的物件可以被拿來用,我們又何必大費周章先做一份拷貝,再刪除原副本呢?這換來的是效率上的巨大提升。使用移動建構函式我們需要注意2點:1). 不能讓移動建構函式丟擲異常,我們將它設為noexcept; 2). “ 偷 ”完東西將原指標設為NULL, 否則要是原物件被delete,“ 偷 ”的東西也就沒了,這讓我們難以接受。2. 如果我們要將一個容器拷貝到另一個容器,將容器內的物件一個一個拷貝?天哪!我們還是來 “ 偷 ” 吧。C++11以後容器都內建有移動建構函式,當我們對容器進行拷貝時,它已經在背後悄悄地 “ 偷 ”了。(舉個例子, 將一個300萬個物件的vector進行拷貝, 是一個一個拷貝好呢, 還是隻需要“ 偷 ” 3個指標好呢(start, finish, end_of_storage)? 果然還是 “ 偷 ” 起來爽呀)
(五)、移動賦值函式。移動賦值的原理同上,也是採用 “ 偷 ” 的方法,尤其注意自我賦值即可。
inline myString& myString::operator= (myString&& rhs) noexcept { if (this != &rhs) { if (m_data) delete m_data; m_data = rhs.m_data; rhs.m_data = NULL; } return *this; }
(六)、解構函式。解構函式的任務就是把申請的物件進行釋放。
inline myString::~myString() { delete m_data; }
這裡給出測試程式碼:
(我們對一些程式碼加了些提示性的語句。 測試環境: VS2017)
#include <iostream> #include <cstring> #include <vector> using namespace std; class myString { public: myString(const char* rhs = 0); myString(const myString& rhs); myString(myString&& rhs) noexcept; myString& operator=(const myString& rhs); myString& operator=(myString&&) noexcept; ~myString(); char* getStr() { return m_data; } private: char* m_data; }; inline myString::myString(const char* rhs) { if (rhs) { m_data = (char*)new char[strlen(rhs) + 1]; strcpy_s(m_data,strlen(rhs)+1, rhs); } else { m_data = new char[1]; *m_data = '\0'; } } inline myString::myString(const myString& rhs) { m_data = (char*)new char[strlen(rhs.m_data) + 1]; strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data); } inline myString::myString(myString&& rhs) noexcept : m_data(rhs.m_data) { rhs.m_data = NULL; cout << " 呼叫了我一次。myString(myString&& rhs) " << endl; } inline myString& myString::operator=(const myString& rhs) { if (this != &rhs) { if (m_data) delete m_data; m_data = (char*)new char[strlen(rhs.m_data) + 1]; strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data); } return *this; } inline myString& myString::operator=(myString&& rhs) noexcept { if (this != &rhs) { if (m_data) delete m_data; m_data = rhs.m_data; rhs.m_data = NULL; } cout << " 呼叫了我一次。operator(myString&& rhs) " << endl; return *this; } inline myString::~myString() { delete m_data; } int main() { myString str1; myString str2("wang"); myString str3(str2); myString str4 = str2; vector<myString> vec; int n = 20; while (n--) // 通過size 和 capacity 的值以及輔助性語句,檢視容器擴充時是否呼叫移動拷貝。 { cout << "vector size = "; cout << vec.size() << endl; cout << "vector capacity = " ; cout << vec.capacity() << endl; cout << endl; vec.push_back(str1); cout << "vector size = "; cout << vec.size() << endl; cout << "vector capacity = "; cout << vec.capacity() << endl; cout << endl; vec.push_back(str2); cout << "vector size = "; cout << vec.size() << endl; cout << "vector capacity = "; cout << vec.capacity() << endl; cout << endl; vec.push_back(str3); cout << "vector size = "; cout << vec.size() << endl; cout << "vector capacity = "; cout << vec.capacity() << endl; cout << endl; vec.push_back(str4); cout << "vector size = "; cout << vec.size() << endl; cout << "vector capacity = "; cout << vec.capacity() << endl; } vector<myString> vec2{ vec }; // 檢視容器賦值時是否呼叫內部移動構造(無提示性語句。 可通過vs2017除錯跟蹤函式呼叫過程) return 0; }