談談c++的初始化工作(2) (轉)

amyz發表於2007-08-15
談談c++的初始化工作(2) (轉)[@more@]


我們首先來看上次遺留的問題。
把(1)中的程式碼換為註釋部分,或許您一時還認識不到會有什麼發生,但最終是通不過的,丟擲異常,資訊如下:
未處理的“System.Runtime.InteropServices.SEHException”型別的異常出現在 TestInit.exe 中

其他資訊:外部發生異常。

This application has requested the Runtime to tenate it in an unusual way.
Please contact the application's support team for more information.
Press any key to continue

我想,您回頭再細看的話,就會明白為什麼如此了(我們寫一定要追問到底:)。

我們今天要談的是,一些變數只有唯一的初始化形式,透過例子,告訴您要特別注意。然後,我們就一步一步,來看資源淺複製的問題。我相信初學c++的同學,會對“複製”有些疑問,它就是為了解決上述問題的;但事實上,還有一個隱藏的地方,今天我也想給您指出。
這些程式,可是我特意設計的哦。希望可以很方便的認識問題所在,與解決之道。

首先,看第一個例子。在類中,這兩類變數:
e.g.
Name &name;  //引用
const int ID;  //常量
它們的初始化形式是唯一的。而且必須由您來初始化。
看下面的程式:
 
//human.h
#pragma once

class Name
{
  char *name;
public:
  //...
};
class Human
{
 Name &name;
 const int ID;//每個人都唯一的標誌號
 //... 
public:
 Human(void);
 ~Human(void);
 
 //...
};
//human.cpp
#include "human.h"
#using

//預設的建構函式
Human::Human(void)
{
}

Human::~Human(void)
{
}

寫一個主測試。
但除錯出錯,錯誤資訊檔案為:
/*----------------------------------------------------------------------------
//Human:error file
------ 已啟動生成:專案:TestInit, :De ------

正在編譯...
Human.cpp
Human.cpp(5) : error C2758: “Human::name” : 必須在建構函式基/成員初始值設定項列表中初始化
  e:Small_codeTestInitHuman.h(13) : 參見“Human::name”的宣告
Human.cpp(5) : error C2758: “Human::ID” : 必須在建構函式基/成員初始值設定項列表中初始化
  e:NETSmall_codeTestInitHuman.h(14) : 參見“Human::ID”的宣告
fmain.cpp
Date.cpp
正在生成程式碼...

生成日誌儲存在“file://e:NETSmall_codeTestInitDebugBuildLog.htm”中
TestInit - 2 錯誤,0 警告


---------------------- 完成 ---------------------

  生成:0 已成功, 1 已失敗, 0 已跳過
--------------------------------------------------------------------------------
*/
 
  因為這裡涉及的是僅僅的c++語法,我就不多費口舌了,如何改正,希望您能動手試試,一定要動手,不要想當然哦~~~
  當然,如果您是愛問題的人,我想您可以這樣深究一下:設計c++語言時,為什麼諸如int型別成員變數能提供預設初始化,而它們卻不能;從編譯角度,刻意給它們提供如int型別般的初始化會有什麼困難和問題?


  下面詳細談什麼是資源淺複製問題。沿襲c的習慣,c++對自分配的資源進行統一管理,但是,申請的資源,則有使用者來釋放。
  比如:

  userType *p=new userType(/*---*/);
  //...
  delete p;
  //delete釋放一般是不可忘的

  單獨的變數或許對您來說是不成問題的。但在類中,這些情況就變的相當複雜。處理不好,您的系統要麼就是因為洩露而執行不下去,而要麼就是異常頻頻發生。
  我們先來看一些c++的預設操作:
  //...
  class OneClass {
  int _value;
  public: 
  OneClass(int _val=0):_value(_val) {}
  ~OneClass() {}

  //...
  };

  //you may use in this way:
  OneClass oneObj(7);
  OneClass anotherObj;
  anotherObj=oneObj;//(1)
  //...
  //int Compare(OneClass one,OneClass two);
  int k=Compare(oneObj,anotherObj);//(2)
  //...

  在本程式的場景下,上面的程式碼是可以完好工作的,但您清楚(1)與(2)系統都作了什麼了嗎?您是否知道,如果您的初始化工作做的不好的話,即使,就沿用上面的初始化習慣,您的程式很容易就崩潰了呢。
  答案是,(1)語句時,預設的,系統試圖把oneObj的資源全部copy給anotherObj,但使用者申請的資源(new來的:),卻傳入的是地址;(2)語句的預設形參傳遞遵循同樣的規則。
  當然這與與是不同的,因為java與c#的是引用型別。而c++,除非您強行定義為引用型別的,它就不是。

  我們來看下面的例子,第一遍我建議您只看程式,不要往下看,看您能否發現什麼問題。

//human.h 
 #pragma once

#define NULL 0

class Name
{
  char *name;
public:
 Name(char *_name=NULL):name(_name) {}
 ~Name() { }

 char *getName(){ return name;}

};
class Human
{
 Name *name;//
 int ID;  //唯一化標誌
public:
 Human(int id=0,char *_name=NULL);
 ~Human(void);

 int getID()const { return ID;}
 Name *getName() { return name;}
};

//human.cpp
#include "human.h"
#using

Human::Human(int id,char *_name):ID(id)
{
  name=new Name(_name);//初始化:指標
}

Human::~Human(void)
{
 delete name;//析構時釋放資源
}

//fmain.cpp
#include
#include "human.h"

void main()

  //測試程式
 try{
 Human lily(11100120,"lily");
 Human lucy=lily;
 }
 catch(...)
 {//如果有any異常
 std::cout< }
 

//請回頭看程式,您覺得一切都好嗎?


  事實上,除錯過程中,等三個異常忽略後,就會得到下面的結果:
/* 
After three exceptions occured you get :

 Unknown Exception...
Press any key to continue

*/
  為什麼?
  看這幾行程式碼:
  Human lily(11100120,"lily");
 Human lucy=lily;
  雖然一開始,我就給了小例子,形式一樣,那個沒問題。何以這個就不行了呢?因為類的定義不同。
  由c++工作的機理,這行
  Human lucy=lily;
  是把lily的資源複製給lucy(lucy是不建構函式的),可是,因為其中的name是使用者申請的資源,並不能把它也複製過去,而是直接傳了地址。這樣,您知道嗎,lucy.name和lily.name的地址是一樣的。
  於是,當一個的解構函式呼叫後,name所指向的資源已被釋放掉了的。而另外一個類的解構函式又去釋放,問題來了---程式崩潰了!
 
  這就是淺複製問題---“淺”的不完全的複製:)。
 
  找到原因,我們就辦法。解決的辦法是,這份工作自己來做!
  寫一個複製賦值操作(public):

  形式為:  className &operator=(className &obj){ /*...*/}
  您看下面的解決辦法和結果:
/*

If you add ...in class Human(public):

 Human &operator=(Human &human){
 if(this!=&human){
  ID=human.getID();
  name=new Name(human.getName()->getName());
  return *this;
 }
 }
 
 
OK,and you get:

Press any key to continue

That is what we want!
*/

  下面的例子,是複製函式的問題,在上面的基礎上,我改動了一下程式的結構,
定義了一個名空間。
  具體的問題分析我留給下次,您就有機會細細的看了。您是否一切都清楚了?
您可以解決這個問題嗎?

//human.h
#pragma once
#using
namespace Humanbeing
{
#define NULL 0

class Name
{
  char *name;
public:
 Name(char *_name=NULL):name(_name) {}
 ~Name() { }

 char *getName(){ return name;}

};
class Human
{
 Name *name; //
 int ID;  //唯一的標誌
public:
 Human(int id=0,char *_name=NULL):ID(id)
 {
 name=new Name(_name);//申請資源
 }
 ~Human(void)
 {
 delete name;//釋放資源
 }

  //複製賦值操作
 Human &operator=(Human &human){
 if(this!=&human){
  ID=human.getID();
  name=new Name(human.getName()->getName());
  return *this;
 }
 }
 
 int getID()const { return ID;}
 Name *getName() { return name;}
};

//名空間裡的函式
bool IsSameMan(Human one,Human another)
{
  if(one.getID()==another.getID())
 return true;
 else return false;
}

}

//測試檔案
#include
#include "human.h"
void main()
{
 using namespace Humanbeing;
 try{
 Human lily(11100120,"lily");
 Human lucy=lily;

 if(IsSameMan(lucy,lily))
 {
 std::cout< }else
 std::cout<

 }
 catch(...)
 {
 std::cout< }
 
}


  除錯結果呢,是連續六個異常後,出現:
After six exceptions occured you get :

 They are the same one.

 Unknown Exception...
Press any key to continue

  為什麼(上次異常是三個,這次是六個,可以解釋嗎)?怎麼辦?  


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-957963/,如需轉載,請註明出處,否則將追究法律責任。

相關文章