1. 程式人生 > >C++知識積累:運算子過載時建構函式與解構函式呼叫次數不一致的問題

C++知識積累:運算子過載時建構函式與解構函式呼叫次數不一致的問題

在學習運算子過載的時候自己寫了這樣一段程式:

class Stu
{
 public:
    Stu()
    {
        std::cout<<"Stu No parameter constructor called!"<<std::endl;
    }
    Stu(int a,std::string b)
    {
        std::cout<<"Stu constructor called!"<<std::endl;
        age=a;
        name=b;
    }
    ~Stu()
    {
        std::cout<<"Stu destructor called!"<<std::endl;
    }
    Stu operator+(const Stu ss)const
    {
        Stu n;
        n.age=age+ss.age;
        n.name=ss.name;
        return n;
    }
    friend int operator+(const Stu s1,const int s);
private:
    int age;
    std::string name;
};
int operator+(const Stu s1,const int s)
{
    int n=1;
    std::cout<<s1.age<<std::endl;
    //n=s1*s2.age;
    return n;
}
int main()
{
    Stu s(3,"aa");
    Stu s2(5,"bb");
    s=s+s2;
    int x=s2+3;
    std::cout<<x<<std::endl;
//    std::cout<<s.age<<" "<<s.name<<" "<<x<<std::endl;
    return 0;
}

執行結果如下:
在這裡插入圖片描述
這就讓我感到很奇怪了。按道理來說我建立的s和s2都有定義相對應的建構函式,那麼建構函式與解構函式的呼叫次數應當是一致的才對,為什麼這裡明顯看出是呼叫了5次解構函式,但是隻呼叫了三次建構函式,那麼多出來的兩次解構函式是從哪來的呢?
帶著這個問題,查了不少資料,發現這是和系統自動建立的匿名物件有關。怎麼來理解呢?我們知道函式的傳參方式一共有三種:值傳遞、地址傳遞和引用傳遞,在C++ Primer 第5版 6.2章節中提到這樣一句話:每次呼叫函式時都會重新建立它的形參,並用傳入的實參對形參進行初始化,形參的型別決定了形參與實參的互動方式,如果形參是引用型別,那麼形參將與實參相互繫結,否則,將實參的值進行拷貝再賦值給形參。


當以類物件作為引數時,也是如此,如果是按值傳遞,那麼形參就會對實參物件進行拷貝,這時候就會呼叫拷貝建構函式構造一個匿名物件,在函式呼叫結束時,該匿名物件再析構;如果是按引用傳遞的話,則形參物件和實參物件繫結,無需再拷貝也無需再析構。
回到這個問題中,可以發現函式過載運算子都是按值傳遞的,因此在理論上會呼叫拷貝建構函式,最後再析構。那麼執行結果中多出來的解構函式就應當是那兩個函式中各自建立的匿名物件析構時呼叫的,為了驗證是否正確,在類定義中加上拷貝建構函式:

    Stu(Stu &s)
    {
        std::cout<<"Stu copy constructor called!"<<std::endl;
    }

再進行執行,結果如下:
在這裡插入圖片描述
可見這下建構函式與解構函式呼叫次數是相同的了,可以來分析一下每一次函式呼叫的情況:
第一、二行Stu constructor called呼叫是因為構造了物件Stu s(3,“aa”);Stu s2(5,“bb”);
第三行Stu copy constructor called呼叫是因為執行s=s+s2時,呼叫了成員函式過載運算子,函式會自動生成一個匿名物件,對實參s2進行了拷貝構造後賦值給形參ss;
第四行Stu No parameter constructor called呼叫是因為在成員函式過載運算子中構造了局部物件n;
第五行Stu destructor called呼叫即是函式呼叫結束時,區域性物件n析構;
第六行Stu destructor called呼叫即是函式呼叫結束時,匿名物件的析構;
第七行Stu copy constructor called是執行int x=s2+3;時呼叫了非成員函式過載運算子,函式自動生成一個匿名物件,對實參s2進行拷貝構造後賦值給形參s1;
第九行Stu destructor called呼叫即是函式呼叫結束時,匿名物件析構;
第十、十一行Stu destructor called呼叫main函式結束,s物件和s2物件析構。
如果將過載運算子函式中的類物件按值傳遞改為按引用傳遞,執行結果如下:
在這裡插入圖片描述
可以發現此時是沒有呼叫拷貝建構函式及其解構函式的,這下即把建構函式與解構函式呼叫次數不一致的問題給解決了。

解決了一個問題,可以發現還有一個小問題:在沒定義拷貝建構函式時,系統是呼叫的預設拷貝建構函式,定義了拷貝建構函式後,系統則呼叫定義的拷貝建構函式,這兩種情況下對比執行結果可以發現:在非成員函式運算子過載中,前者輸出形參s1.age值與實參s2.age值相等,而後者則輸出的是0,而當後面使用引用傳遞引數時,形參s1.age值又與實參值s2.age相等了。這又是什麼原因呢?
將源程式中自定義的拷貝建構函式註釋掉,即使用系統預設的拷貝建構函式,在過載運算子函式中輸出形參的值:
int operator+(const Stu s1,const int s)
{
int n=1;
std::cout<<s1.age<<" "<<s1.name<<std::endl;
return n;
}
輸出結果為
在這裡插入圖片描述
由此可見,匿名物件呼叫的系統預設拷貝建構函式會將實參物件的成員值均進行拷貝後賦值給形參,而自定義的拷貝建構函式由於未進行任何賦值操作,因此形參物件的成員值均為0(字串為空)。