溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++默認(rèn)構(gòu)造函數(shù)的合成

發(fā)布時(shí)間:2020-08-01 13:33:58 來源:網(wǎng)絡(luò) 閱讀:551 作者:GuangYao_Li 欄目:編程語言

默認(rèn)構(gòu)造函數(shù)的誤解

1.當(dāng)程序猿定義了默認(rèn)構(gòu)造函數(shù),編譯器就會(huì)直接使用此默認(rèn)構(gòu)造函數(shù)

來一個(gè)簡單的栗子

class Student;
class School
{
public:
School(){}
...
Student students;
};

我們知道,一個(gè)對象,在定義的時(shí)候就一定會(huì)調(diào)用其構(gòu)造函數(shù)。而在我們上面的默認(rèn)構(gòu)造函數(shù),明顯沒有調(diào)用students的構(gòu)造函數(shù),所以,編譯器絕對沒有直接使用我們的默認(rèn)構(gòu)造函數(shù)。具體細(xì)節(jié),請看下文,這里只提問題。

2.當(dāng)程序猿沒有定義構(gòu)造函數(shù)的時(shí)候,編譯器就會(huì)幫我們定義默認(rèn)構(gòu)造函數(shù)

接下來,就讓我們帶著這些錯(cuò)誤的看法來進(jìn)入正文。

為什么要幫我們定義默認(rèn)構(gòu)造函數(shù)

再來個(gè)簡單的栗子

class Student
{
public:
Student(){} //有定義
void Study(); //只給出了聲明,沒有定義
...
};
void main()
{
Student stu;
//stu.Study(); //調(diào)用沒有定義的函數(shù)
}

上面是一個(gè)可以編譯,連接,運(yùn)行的例子完整代碼。其中,Study()函數(shù)只有聲明,但是沒有定義,但是卻通過了編譯?為什么呢?因?yàn)槟銢]有用到它。即使,你將注釋的那行代碼取消注釋,它也不會(huì)在編譯期出錯(cuò),只會(huì)等到連接的時(shí)候編譯器才會(huì)提示錯(cuò)誤。具體可以參考這篇博客,依樣畫葫蘆。你也可以先不看,記住這樣的話:編譯器沒有具體需要用到某個(gè)函數(shù)時(shí)(上面是因?yàn)榇a中沒有調(diào)用Study函數(shù)),這個(gè)函數(shù)可以沒有實(shí)現(xiàn)。所以,你可以在你的代碼中很不負(fù)責(zé)任地聲明很多沒有用的函數(shù)并且不對其中的任何一個(gè)進(jìn)行實(shí)現(xiàn)。

回到我們的內(nèi)容,“為什么要幫我們定義默認(rèn)構(gòu)造函數(shù)”,答案就是編譯器要用到,但是你卻沒有給出明確定義。注意,這里不是程序需要,而是編譯器需要。程序需要用到,是指我們希望class中的成員,基類等能夠正常地值初始化。而編譯器需要,是指,沒有這個(gè)函數(shù),編譯連接工作就沒辦法正常地進(jìn)行。

那么問題就來了,編譯器具體什么時(shí)候有這個(gè)需求。

在四個(gè)需求下,編譯器需要一個(gè)默認(rèn)構(gòu)造函數(shù)

第一個(gè)需求

如果一個(gè)class沒有任何constructor,但它內(nèi)含一個(gè)member object,而后者有default constructor,那么這個(gè)class的implicit default constructor就是“nontrivial",編譯器需要為該class合成一個(gè)default constructor。不過,這個(gè)合成操作只有在constructor真正需要被調(diào)用時(shí)才會(huì)發(fā)生。

這里引用了書里的話。nontrivial的意思就是有用的。舉個(gè)例子說明一下。

class Student
{
public:
Student(){}
...
};
class School
{
Student students; //不是繼承,是內(nèi)含
char* name;
...
};
void main()
{
int a;
School school; //合成操作在這里發(fā)生
}

上面的例子中,編譯器為School類合成了一個(gè)default constructor,因?yàn)镾chool中包含的Student具有默認(rèn)的構(gòu)造函數(shù),而我們在構(gòu)造School的時(shí)候,需要調(diào)用Student的默認(rèn)構(gòu)造函數(shù),所以編譯器就幫我們合成了一個(gè)大概樣子如下的默認(rèn)構(gòu)造函數(shù)。

School::School()
{
students.Student();
}

注意:School::name初始化是程序的需求,而不是編譯器的需求。所以,合成的構(gòu)造函數(shù)不會(huì)完成對name的初始化。時(shí)刻分清,編譯器的需求與程序的需求不是一回事。

回到上面的程序,編譯器在main中的第二行代碼中才進(jìn)行了合成。還記得在上一部分中我們提到的那些只聲明沒有定義的函數(shù),無獨(dú)有偶,假如我們在上面的代碼中沒有實(shí)例化School,那么這個(gè)合成操作永遠(yuǎn)不會(huì)進(jìn)行,因?yàn)榫幾g器不需要?。?!只有當(dāng)需要用到這個(gè)默認(rèn)構(gòu)造函數(shù)的時(shí)候,編譯器才會(huì)進(jìn)行合成。

這里還有一個(gè)問題,假如我們自己定了構(gòu)造函數(shù),卻沒有調(diào)用內(nèi)部對象的構(gòu)造函數(shù)時(shí),編譯器還會(huì)合成一個(gè)新的構(gòu)造函數(shù)嗎?否。編譯器只會(huì)在已有的構(gòu)造函數(shù)里面插入”編譯器需要“的代碼。再來個(gè)簡單的栗子。

class Student
{
public:
Student(){}
...
};
class School
{
public:
School(){name = NULL} //沒有初始化students
Student students; //不是繼承,是內(nèi)含
Student students2;
char* name;
...
};
//編譯器插入自己需要的代碼,最后的構(gòu)造函數(shù)類似如下
School::School()
{
//在用戶自己的代碼前插入,確保所有的對象在使用前已經(jīng)初始化
students.Student();
students2.Student(); //存在多個(gè)對象,按照聲明的順序進(jìn)行插入
name = NULL;
}

第二個(gè)需求

如果一個(gè)沒有任何constructor的class派生自一個(gè)"帶有default constructor"的base class,那么這個(gè)derived class的default constructor會(huì)被視為nontrivial,并因此需要被合成出來。

這一點(diǎn)與第一個(gè)需求很相似。需要記住的有以下幾點(diǎn)。

1.在derived class的constructor(已有或者合成)中,編譯器除了插入member class object的constructor外,還會(huì)插入base class constructor。

2.base class constructor的調(diào)用時(shí)間在member class object之前。

第三個(gè)需求

class聲明(或繼承)一個(gè)virtual function,當(dāng)缺乏程序猿聲明的constructor,編譯器合成一個(gè)default constructor。

我們知道,virtual function在調(diào)用的過程中,具體的函數(shù)是在編譯器是不可知的。比如

class Base
{
public:
Base();
virtual void Print();
};
class Derived:public Base
{
public:
Derived();
virtual void Print();
};
void Print(Base *para)
{
para->Print();
}
void main()
{
Base a;
Derived b;
Print(a); //調(diào)用Base::Print();
Print(b); //調(diào)用Derived::Print();
}

編譯器如何得知調(diào)用哪一個(gè)Print()呢?當(dāng)class中含有virtual function的時(shí)候,編譯器會(huì)在class中插入“虛表指針",并在構(gòu)造函數(shù)中進(jìn)行初始化。虛表指針指向了虛函數(shù)表,里面記錄了各個(gè)虛函數(shù)的地址。程序之所以能夠?qū)崿F(xiàn)多態(tài),實(shí)際上就是因?yàn)檎{(diào)用虛函數(shù)的時(shí)候,動(dòng)態(tài)地使用了這個(gè)表里面的地址。

回歸一下正題,在這里要強(qiáng)調(diào)的是,當(dāng)存在虛函數(shù)的時(shí)候,編譯器需要構(gòu)造虛表指針。所以,假如我們沒有任何構(gòu)造函數(shù)的時(shí)候,編譯器就會(huì)合成一個(gè)默認(rèn)構(gòu)造函數(shù),里面滿足除了前面“第一個(gè)需求,第二個(gè)需求”外,還會(huì)在在Base class完成構(gòu)造之后,完成虛表指針的初始化。假如我們已經(jīng)定義了構(gòu)造函數(shù),那么就會(huì)在base class constructors之后,member initialzation list之前完成虛表指針的初始化。

第四個(gè)需求

class派生自一個(gè)繼承串鏈,其中有一個(gè)或更多的virtual base classes,當(dāng)沒有程序猿定義的constructor的時(shí)候,編譯器就會(huì)合成一個(gè)默認(rèn)構(gòu)造函數(shù)。舉個(gè)例子

class X
{
public:
int i;
};
class A:public virtual X{};
class B:public virtual X{};
class C:public A,public B{};
void Foo(const A *pa)
{
pa->i = 1024;
}
void main()
{
Foo(new A);
Foo(new C);
}

編譯器沒辦法確切地知道“經(jīng)由pa"而存取的X::i的實(shí)際偏移位置,因?yàn)閜a的真正類型可以改變。編譯器必須改變”執(zhí)行存取操作“的那些代碼,使X::i可以延遲至執(zhí)行期才決定下來。對于class定義的每個(gè)constructor,編譯器會(huì)安插那些”允許每一個(gè)virtual base class“執(zhí)行期存取操作的代碼。如何class沒有聲明任何constructor,編譯器必須為它合成一個(gè)default constructor。

C++默認(rèn)構(gòu)造函數(shù)

默認(rèn)構(gòu)造函數(shù)

默認(rèn)的構(gòu)造函數(shù)是指為所有參數(shù)都提供了默認(rèn)值的構(gòu)造函數(shù),通常是指無參的構(gòu)造函數(shù)。比如下面的類Test,它的默認(rèn)構(gòu)造函數(shù)就是Test()。

class Test
{
public:
Test(){} // default constructor
} ;

如果你沒有為你的類提供任何構(gòu)造函數(shù),那么編譯器將自動(dòng)為你生成一個(gè)默認(rèn)的無參構(gòu)造函數(shù)。一旦你為你的類定義了構(gòu)造函數(shù),哪怕只是一個(gè),那么編譯器將不再生成默認(rèn)的構(gòu)造函數(shù)。

為你的類提供默認(rèn)的構(gòu)造函數(shù)

有很多原因,列舉如下:

  1. 當(dāng)你使用靜態(tài)分配的數(shù)組,而數(shù)組元素類型是某個(gè)類的對象時(shí),就要調(diào)用默認(rèn)的構(gòu)造函數(shù),比如下面的代碼。

Object buffer[10]; // call default constructor

  1. 當(dāng)你使用動(dòng)態(tài)分配的數(shù)組,而數(shù)組元素類型是某個(gè)類的對象時(shí),就要調(diào)用默認(rèn)的構(gòu)造函數(shù),比如下面的代碼,如果Object沒有默認(rèn)的構(gòu)造函數(shù),是無法通過編譯的,因?yàn)閚ew操作符要調(diào)用Object類的無參構(gòu)造函數(shù)類初始化每個(gè)數(shù)組元素。

Object* buffer = new Object[10];

  1. 當(dāng)你使用標(biāo)準(zhǔn)庫的容器時(shí),如果容器內(nèi)的元素類型是某個(gè)類的對象時(shí),那么這個(gè)類就需要默認(rèn)的構(gòu)造函數(shù),原因同上。

vector<Object> buffer;

  1. 一個(gè)類A以另外某個(gè)類B的對象為成員時(shí),如果A提供了無參構(gòu)造函數(shù),而B未提供,那么A則無法使用自己的無參構(gòu)造函數(shù)。下面的代碼將導(dǎo)致編譯錯(cuò)誤。

class B
{
B(int i){}
};

class A
{
A(){}
B b;
};

int main(void)
{
A a(); // error C2512: 'B' : no appropriate default constructor available

getchar() ; 
return 0 ; 

}

再比如下面的代碼,類A定義了拷貝構(gòu)造函數(shù),而沒有提供默認(rèn)的構(gòu)造函數(shù),B繼承自A,所以B在初始化時(shí)要調(diào)用A的構(gòu)造函數(shù)來初始化A,而A沒有默認(rèn)的構(gòu)造函數(shù),故產(chǎn)生編譯錯(cuò)誤。

class A
{
A(const A&){}
};

class B : public A
{

};

int main(void)
{
B b; //error C2512:'B': no appropriate default constructor available

getchar() ; 
return 0 ; 

}

以上是云棲社區(qū)小編為您精心準(zhǔn)備的的內(nèi)容,在云棲社區(qū)的博客、問答、公眾號(hào)、人物、課程等欄目也有的相關(guān)內(nèi)容,歡迎繼續(xù)使用右上角搜索按鈕進(jìn)行搜索編譯器 , class , 函數(shù) , 程序 , 代碼 virtual 合成的默認(rèn)構(gòu)造函數(shù)、合成默認(rèn)構(gòu)造函數(shù)、c 默認(rèn)構(gòu)造函數(shù)、c 默認(rèn)拷貝構(gòu)造函數(shù)、c 不存在默認(rèn)構(gòu)造函數(shù),以便于您獲取更多的相關(guān)知識(shí)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI