1. 程式人生 > >淺談類的幾個基礎建構函式

淺談類的幾個基礎建構函式

通過例子來介紹下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(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'; } }

  這裡僅是申請了一塊記憶體,對傳入字串進行了拷貝。

 

 

(二)、關於拷貝建構函式。拷貝建構函式是僅是對於傳入物件的一次深拷貝。記得使用引用傳入,由於我們不需要對傳入物件進行修改操作,那就對它宣告為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;
}