C++
和
C#/java
有很多區別,其中最大的區別當數對內存的管理。
C++
中,類的使用者決定了類的實例內存會如何分配,分配在堆上還是棧上。我們先看一段例子程序:
?
#include
"stdio.h"
?
class
Demo{
public
:
???
int i;
???
char* objName;
??? Demo(){
??????? objName = "Default object.";
??????? printf("%s, objName = %s\r\n", "Enter Demo default ctor. method.", objName);
???????
??????? i = 1000;
??? }
?
??? Demo(int ival, char* name){
??????? printf("%s,i = %d, objName = %s\r\n", "Enter Demo(int ival) ctor method", ival, name);
??????? i = ival;
??????? objName = name;
??? }
?
??? Demo(const Demo& d){
??????? printf("%s\r\n", "Enter Demo copy ctor method.");
??????? i = d.i;
??????? objName = "copied d";
??? }
?
??? ~Demo(){
??????? printf("%s, i = %d, objName = %s\r\n", "Enter Demo dector. method" , i, objName);
??? }
};
?
Demo& testMethod0(){
??? printf("%s\r\n", "Enter testMethod0.");
??? Demo d(0, "d in testMethod0");
??? printf("%s\r\n", "Exit testMethod0.");
???
return d;
}
?
Demo testMethod1(){
??? printf("%s\r\n", "Enter testMethod1.");
??? Demo d(1, "d in testMethod1");
??? printf("%s\r\n", "Exit testMethod1.");
???
return d;
}
?
Demo* testMethod2(){
??? printf("%s\r\n", "Enter testMethod2.");
??? Demo *d = new Demo(2, "d in testMethod2");
??? printf("%s\r\n", "Exit testMethod2.");
???
return d;
}
?
int
main(int argc, _TCHAR* argv[])
{
??? Demo d;
??? d = testMethod1();
?
??? Demo& d1 = testMethod0();
?
??? Demo d2(999, "d1");
?
??? Demo* d3 = testMethod2();
?
??? printf("d.i = %d\r\n", d.i);
??? printf("d1.i = %d\r\n", d1.i);
??? printf("d2.i = %d\r\n", d2.i);
??? printf("d3.i = %d\r\n", d3->i);
?
???
delete d3;
???
return 0;
}
?
Output
:
Enter Demo default ctor. method., objName = Default object.
Enter testMethod1.
Enter Demo(int ival) ctor method,i = 1, objName = d in testMethod1
Exit testMethod1.
Enter Demo copy ctor method.
Enter Demo dector. method, i = 1, objName = d in testMethod1
Enter Demo dector. method, i = 1, objName = copied d
Enter testMethod0.
Enter Demo(int ival) ctor method,i = 0, objName = d in testMethod0
Exit testMethod0.
Enter Demo dector. method, i = 0, objName = d in testMethod0
Enter Demo(int ival) ctor method,i = 999, objName = d1
Enter testMethod2
Enter Demo(int ival) ctor method,i = 2, objName = d in testMethod2
Exit testMethod2.
d.i = 1
d1.i = -2
d2.i = 999
d3.i = 2
Enter Demo dector. method, i = 2, objName = d in testMethod2
Enter Demo dector. method, i = 999, objName = d1
Enter Demo dector. method, i = 1, objName = copied d
?
與
C#
不同,在
C++
中,對象聲明的時候就已經執行了構造函數,比如上面例子的
main
函數中的第一行,
Demo d
,從屏幕上的輸出來看,這個時候
Demo class
的默認構造函數會被調用。
接下來的一行代碼調用,引出了很有趣的情況,當然也隱藏著不小的問題。這行代碼造成了一次構造函數調用,一次拷貝構造函數調用和兩次析構函數調用。讓我們來具體分析一下:第一次調用構造函數很容易理解,因為在
testMethod1
中我們聲明了
Demo d(0)
,退出
testMethod1
,函數的返回值要賦值給變量
d2
,這個時候,
d2
被拷貝構造函數重新構造了一次。接著
testMethod1
中構造的局部變量被析構,然后,居然拷貝構造函數構造的對象也被析構?等等,看完所有輸出,我們發現,
objName = copied d
的對象被析構兩次,而
objName = Default obj
的對象被構造出之后沒有被析構,這里隱藏了很嚴重的問題,有可能導致內存泄漏、句柄不能被正確關閉等等。另外,拷貝構造函數的執行可能導致潛在的效率問題,考慮一個包含巨大矩陣的對象,
copy
這個對象會怎么樣?
?
接下來的一行代碼,
testMethod0
返回一個對象的引用,當然不會導致拷貝構造函數被調用,但是,這樣也是有問題的,在函數中聲明的局部變量在函數執行完成的時候會被析構,那么直接返回局部變量就可能會出現問題。
testMethod0
退出以后,他內部的
Demo
對象就會自動析構,外面對它的引用當然也無法指向正確的對象了,所以后面程序打印
d.i
的時候,輸出了一個莫名其妙的
-2
。
?
效率最好的方法當數返回指針了,它不會導致對象復制,如果使用得當,也不會導致內存泄漏或者句柄泄漏。
testMethod2
演示了這種情況,當然,你需要手工刪除在
testMethod2
中創建的對象。
?
?