copy constuctor問題

http://codepad.org/UxTuy4MK

已上網址是程式碼,想請教這裡的前輩們

問題一:

小弟只知道資料成員有指標char *name,所以需做copy constuctor,但不知此程式碼造成的理由是什麼????為什麼要這麼做

問題二:

Emp::Emp(const Emp& e )

{

age=e.age;

name=new char [strlen(e.name)+1];

assert(name != 0);

strcpy_s(name,strlen(e.name)+1, e.name);

}

請問e.age 的age 是Emp&e這行所建立出物件e的age嗎????

age=e.age這行等號左邊的age是類別的資料成員age嗎???

因二者age我分不太清楚

name=new char [strlen(e.name)+1];

這行等號左邊的name也是類別的資料成員name吧???

Update:

請問帕拉提斯前輩,因我是新手很多盲點

在此程式碼裡第 25 行位什麼要在這裡會new 一塊 char []。???動機是什麼???

在 test() 回傳時候解構 ea,釋放的是 copy c-tor 配置出的記憶體。這怎麼知道釋放的是 copy c-tor 配置出的記憶體???

而且第44行的test() 函數是void型態位什麼會有傳回???

在 main() 回傳的時候解構 e,釋放的是 51 行宣告 e 時配置出的記憶體。這怎麼知道釋放的是 51 行宣告 e 時配置出的記憶體???main() 回傳的時候解構 e為什麼???

Update 2:

main() 為什麼會做回傳得動作

2 Answers

Rating
  • 其威
    Lv 7
    9 years ago
    Favorite Answer

    預設的複製建構子是依照宣告順序呼叫所有成員的 operator =。

    以你的 class 來說,先宣告了一個 int age,又宣告了一個 char *name,所以他預設的 operator = 相當於這樣:

    Emp & Emp::operator = (Emp const & rhs)

    {

    this->age = rhs.age; // 1

    this->name = rhs.name; // 2

    return *this;

    }

    如果你變數的宣告順序是 char *name 寫在 int age 前面,那麼 1 跟 2 兩行的次序就會交換。

    但是,這會造成你當前的 object 與複製的 object 的 name 都指到同一塊記憶體。

    然後根據你的 code,你在 d-tor 中會 delete [] 該指標,所以這塊記憶體會被 delete [] 兩次(原本的物件解構時呼叫 delete [] 一次、複製出的物件解構時 delete [] 一次)。

    這是一個相當有名的執行階段錯誤,叫做「double free error(多次釋放錯誤)」。

    以你的程式來說,第 51 行會建構一個 Emp 物件(使用 Emp::Emp(int, char*)),在這裡會 new 一塊 char []。

    第 53 行會呼叫 44 行的 void test(Emp) 函式,這個函式會將外部傳入的 Emp 物件複製一份作為參數(呼叫 copy c-tor)。

    如果你使用 trivial 的 copy c-tor,那麼 51 行宣告的 e 與 44 行傳進去的 ea,兩個不同物件的 name 都指到同一塊記憶體(在 51 行配置 e 的時候配置的記憶體)。

    然後,test() 回傳,這時他會解構一切用到的區域變數,也就是他會 delete [] name。

    然後,這塊記憶體就變得不可用了,可是 e.name 還是指到他。56 行裡面的 e.GetName() 就會參照到無效的記憶體。

    可能會印出莫名其妙的字串、可能程式會當掉、可能電腦會爆炸...

    最後若是你的程式沒當掉,main() 總也是要回傳的嘛~

    這時候他又會呼叫 Emp::~Emp() 來解構 e。

    災難就這樣發生了!你又 delete [] 了一次已經釋放過(不屬於你)的記憶體。

    你的 code 希望的正確行為,應該是先配置一塊新的、足夠放置 name 字串的記憶體空間,再將 name 字串整個複製過去。

    也就是跟你的程式碼中寫的那樣...

    那麼在 test() 回傳時候解構 ea,釋放的是 copy c-tor 配置出的記憶體。

    在 56 行 e.GetName() 的時候使用的是有效的記憶體。

    在 main() 回傳的時候解構 e,釋放的是 51 行宣告 e 時配置出的記憶體。

    皆大歡喜!一點問題都沒有~

    並不是因為資料成員中有指標就要複寫 copy c-tor,而是「預設的行為不是你要的行為」的時候要複寫 copy c-tor。

    而「經驗中」若是你的程式中有 new / delete 東西,那麼你「大概會」需要複寫 copy c-tor,因為預設的動作「一般來說」不正確。

    當然,這也會有例外,例如你在實做某個 Copy-on-Write 的 string class,那可能就可以直接使用 trivial 的 copy c-tor,直到呼叫各式 assignment operator 的時候再複製、配置記憶體。

    但是你可能還是會需要在 copy c-tor 中做一些其他操作才能正確實做 CoW string,所以 trivial copy c-tor 應該是不夠。

    === 分隔線 ===

    const Emp & e 並不會產生任何物件,他只會給你外部傳進來的變數取個別名。

    當你看到 & 的時候,你就知道傳進來的東西是個參考,也就是他只是另一個變數的別名罷了。

    這應該可以回答你的第一個問題:

    age = e.age; 之中的 e.age 是外面傳進來的變數的 age。

    再來是 age = e.age; 之中等號左邊的 age。

    在 member function 中等同於是在 class 的範疇之下,所以 class 下的一切名稱都會被引入。

    所以,光寫 age 相當於寫 this->age,光寫 GetName() 相當於寫 this->GetName()。

    下面的 name = new char[ strlen(e.name) + 1 ]; 中等號左邊的 name 也是如此,他表示 this->name。

    2011-10-05 03:37:34 補充:

    額外有個提醒。

    free(C)、delete(C++)與 delete [](C++)這三個釋放記憶體的操作,傳入 NULL pointer 是不會有任何事情發生的。

    這三個操作裡面本身就有類似 if (p != NULL) delete p; 這種邏輯存在,額外檢查傳入指標是否是空指標是多此一舉。

    所以,d-tor 可以直接寫

    Emp::~Emp()

    {

     delete [] name;

    }

    就好。

    2011-10-05 18:40:14 補充:

    其實兩個 new [] 都是同樣的意思 - 你需要一些地方來放 name。

    在 class 中寫 char *name; 他只會配置足夠放一個指標的空間。

    你要放置實際的字串,必須自己準備一塊足夠大的記憶體出來。

    2011-10-05 18:45:40 補充:

    而有關 test() 回傳時釋放的到底是誰的記憶體...

    test() 接收一個 Emp 參數,動作是複製一份傳進來的參數以供 test() 函式使用。

    這就好像你上網下載一個檔案,對方伺服器不是將這個檔案「給你」,而是將這個檔案複製一份傳出來,你得到的是一份全新的重製品。

    所以,當這個函式結束的時候,內部的名稱空間也一併結束,所有名稱空間內的自動變數都會自動被釋放。

    這時候他會釋放你得到的重製品(刪除你硬碟中的檔案),而不會釋放傳進來的變數(伺服器上的檔案)

    2011-10-05 18:52:14 補充:

    main() 為什麼會做回傳的動作... 人為什麼都會死?

    所有的函式呼叫都會有一個結束的時機,不管是正常(回傳)還是不正常(例外狀況、signal 等)。

    main() 也是個函式,他當然也會結束。

    當函式結束的時候(無論正常還是不正常),編譯器都會釋放所有自動變數。

    2011-10-05 18:59:46 補充:

    至於怎麼為什麼知道他會釋放 e......

    e 是個自動變數,自動變數在當前範疇結束時會被釋放。

    而 e 是在哪個範疇之下呢?是在 int main() 函式呼叫的範疇之下。

    「範疇」這東西你可以視為大括號內的區間,你在某個大括號的範圍內配置的自動變數,在這個大括號結束(也就是碰到對應的 })之前會被釋放。

    編譯器可以自己決定什麼時候釋放,但是保證在範疇結束之前。

    所以這個釋放動作有可能發生在最後一次使用該變數至範疇結束前這段區域。

    2011-10-05 19:05:14 補充:

    意見 002 中 age = e.age; 的問題...

    這兩個 age 都是 Emp::age,但是所屬的物件實體不同。

    例如你要照著某個櫃子造出另一個一模一樣的櫃子,櫃子可能會有一些相同的零件(例如門)。

    那麼原版櫃子的門跟複製品的門是同一個門嗎?是也不是。

    是:他們都是同款式、同材料的門

    不是:一扇門屬於原版的櫃子,另一扇門屬於複製出的櫃子。

    2011-10-05 19:16:24 補充:

    void 函式也會回傳,只是他沒有回傳值。

    就像你被妹拉壯丁做司機(或修電腦?),載到定點(修好)之後妹對你說「好了,你沒用了,可以走了。」

    沒有得到任何東西(回傳值),但是這件事情(函式)結束(回傳)了,你可以接著做別的事。

    2011-10-05 20:27:20 補充:

    你這個說法顛倒了主從關係。

    通常我們會說「小明去保健室做了健康檢查」,而不是「保健室是給小明做健康檢查用的」。

    所以說:

    第 51 行會呼叫第 23 行定義的 c-tor。

    第 53 行會呼叫第 44 行定義的 test() 函式,傳參數的時候會呼叫第 30 行定義的 copy c-tor。

    2011-10-05 22:31:14 補充:

    這問題應該沒什麼懸念啊?

  • 9 years ago

    接續補充內容問題.....

    age = e.age; 之中的 e.age 是外面傳進來的變數的 age。外面傳進來的變數的 age是資料成員跟等號左邊的age也是資料成員所以兩個是一樣的吧???

    程式執行53行與44行就會造成指標name指向同一塊記憶體對吧???

    程式碼裡第 30 行位什麼要在這裡又要new 一塊 char []。???

    2011-10-05 19:25:09 補充:

    前輩~!!

    小弟懂了,真的很感謝你,可使小弟進一步邁進

    最後一問:

    程式碼第23行是給Emp e(20,"Bill Gate");呼叫建構子用的對吧???

    程式碼第30行是給函數test(e)呼叫void test(Emp ea)呼叫copy constructor用的對吧???

    2011-10-05 21:44:44 補充:

    請問前輩....容許我在問個較detail問題

    第25行的name與第33行的name是不同的吧

    不同的原因是第25行的name的物件是main()函數的e

    而第33行的name的物件是Emp::Emp(const Emp& e)

    函數的e吧???

    所以會建立出兩塊不同的記憶體,兩個物件各指各的記憶體

Still have questions? Get your answers by asking now.