copy from(https://blog.csdn.net/billcyj/article/details/78888074)
15.7 構(gòu)造函數(shù)和拷貝控制
創(chuàng)建具篇、拷貝度迂、移動击狮、賦值和銷毀
15.7.1 虛析構(gòu)函數(shù)
繼承體系中的析構(gòu)函數(shù)應(yīng)該定義為虛函數(shù)。
以前說過今穿,定義了析構(gòu)函數(shù)就要定義拷貝和賦值沪袭,但這里基類的析構(gòu)函數(shù)是例外。
為什么?
保證可以動態(tài)分配繼承體系中的對象布疙。且delete時能執(zhí)行對應(yīng)的析構(gòu)函數(shù)。
若指針指向繼承體系中的某個類型愿卸,則可能出現(xiàn)指針的靜態(tài)類型和被刪除對象的動態(tài)類型不符合:
如:delete一個Quote*類型指針灵临,該指針指向Bulk_quote類型對象。那應(yīng)該執(zhí)行Bulk_quote的析構(gòu)函數(shù)趴荸。
virtual ~Quote()=default;
15.7.2 合成拷貝控制與繼承
- 派生類中定義為刪除的拷貝控制與基類的關(guān)系
class B
{
public:
B(); //默認(rèn)構(gòu)造函數(shù)聲明
B(const B&) = delete; //定義為刪除的拷貝構(gòu)造函數(shù)
//既然定義了拷貝構(gòu)造函數(shù)儒溉,就不會合成移動操作了
};
class D : public B
{
//沒有聲明任何東西,只是單純繼承了B
};
D d; //正確:D的合成默認(rèn)構(gòu)造函數(shù)調(diào)用B的默認(rèn)構(gòu)造函數(shù)发钝,自己反正也沒成員
D d2(d); //錯誤:因為B的拷貝構(gòu)造函數(shù)是delete的顿涣,所以D的也是delete,無法被調(diào)用
- 移動操作與繼承
移動操作:基類多有虛析構(gòu)函數(shù)酝豪,所以不會合成移動操作涛碑,如果確實需要,應(yīng)首先在基類中顯式定義孵淘,此時要同時顯式定義拷貝操作:
class Quote
{
public:
Quote() = default; //強行合成默認(rèn)構(gòu)造函數(shù)
Quote(const Quote&) = default; //拷貝構(gòu)造函數(shù)
Quote(Quote&&) = default; //移動構(gòu)造函數(shù)
Quote& operator=(const Quote&) = default; //拷貝賦值
Quote& operator=(Quote&&) = default; //移動賦值
virtual ~Quote() = default; //虛析構(gòu)函數(shù)
};
如此锌唾,Quote的派生類也會自動獲得合成的移動操作。
15.7.3 派生類的拷貝控制成員
派生類的拷貝和移動構(gòu)造函數(shù)在拷貝和移動自有成員的同時夺英,也要拷貝和移動基類部分晌涕,賦值運算符也類似。
而析構(gòu)函數(shù)只負(fù)責(zé)銷毀派生類自己分配的資源 基類部分自動銷毀
- 定義派生類的拷貝或移動構(gòu)造函數(shù)
class Base
{
};
class D : public Base
{
//我們要拷貝或移動基類部分痛悯,就必須在派生類的構(gòu)造函數(shù)初始值列表中顯式調(diào)用
public:
D(const D& d) : Base(d) {} //調(diào)用基類的拷貝構(gòu)造函數(shù)來拷貝基類部分
//這兒比較特殊的是Base(d)會去匹配Base的拷貝構(gòu)造函數(shù)余黎,雖然人家其實接受B類型
D(D&& d) : Base(std::move(d)){}
};
- 派生類賦值運算符
也就是拷貝賦值和移動賦值。
D &D::operator=(const D &rhs)
{
Base::operator=(rhs); //顯式地為基類部分賦值:(合成的或自定義的)基類的拷貝賦值運算符將釋
//放掉左側(cè)對象的基類部分的舊值载萌,然后利用rhs為其賦一個新值
//接下來為派生類自己的部分賦值(省略)
return *this;
}
- 派生類析構(gòu)函數(shù)
對象銷毀的順序與創(chuàng)建順序相反:派生類析構(gòu)函數(shù)先執(zhí)行惧财,然后執(zhí)行基類的析構(gòu)函數(shù)巡扇。
也就是,對直接基類部分拷貝后垮衷,再拷貝類本身的成員厅翔;銷毀本身后再銷毀基類,基類部分會自動銷毀搀突。
禁止在構(gòu)造或析構(gòu)函數(shù)中調(diào)用虛函數(shù)
假設(shè)我們在基類的構(gòu)造函數(shù)中調(diào)用派生類的某個函數(shù)去訪問其成員刀闷,這會派生類對象還沒構(gòu)建完成,怎么能訪問呢仰迁?于是甸昏,C++禁止了這種行為。
15.7.4 繼承的構(gòu)造函數(shù)
1.派生類類不能繼承基類的默認(rèn)構(gòu)造函數(shù)徐许、拷貝構(gòu)造函數(shù)施蜜、移動構(gòu)造函數(shù);
2.但派生類能夠重用其基類自定義的構(gòu)造函數(shù)雌隅;
3.派生類中的基類成員調(diào)用基類的構(gòu)造函數(shù)初始化翻默,派生類中的其他成員將被默認(rèn)初始化
class Bulk_quote : public Disc_quote
{
public:
using Disc_quote::Disc_quote; //使用using繼承Disc_quote的構(gòu)造函數(shù)
};
把using放到構(gòu)造函數(shù)上時,using聲明語句將令編譯器產(chǎn)生代碼恰起,而且修械,編譯器會生成一個與基類對應(yīng)的派生類構(gòu)造函數(shù),針對上面的例子生成的派生類構(gòu)造函數(shù)如下
Bulk_quote(const string& book, double price, size_t qty, double disc) : Disc_quote(book, price, qty, disc) {}
- 繼承的構(gòu)造函數(shù)的特點
1.構(gòu)造函數(shù)的using聲明不會改變該構(gòu)造函數(shù)的訪問級別
2.如果基類的構(gòu)造函數(shù)是explicit或constexpr的村缸,則繼承的構(gòu)造函數(shù)也是
3.當(dāng)一個基類構(gòu)造函數(shù)含有默認(rèn)實參,派生類將獲得多個繼承的構(gòu)造函數(shù)武氓,其中每個構(gòu)造函數(shù)分別忽略掉一個含有默認(rèn)實參的形參
另外梯皿,如果派生類自己又定義了一個和基類構(gòu)造函數(shù)具有相同參數(shù)列表的構(gòu)造函數(shù),那么基類的這個構(gòu)造函數(shù)不會被繼承县恕,就用派生類自己定義的就好了东羹,覆蓋了
15.8 容器與繼承
我們不能把具有繼承關(guān)系的多種類型的對象直接存放在容器中:
vector<Quote> basket;
basket.push_back(Bulk_quote("a", 50, 10, 0.25));
basket的元素是Quote對象,因此當(dāng)我們向其中添加Bulk_quote對象時忠烛,屬于派生類的部分會被忽略
容器中應(yīng)放置指針(最好是智能指針)而非對象
vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("a", 50));
basket.push_back(make_shared<Quote>("b", 50, 10, 0.25));
cout << basket.back()->net_price(15) << endl; //打印折扣后的價格属提,這回派生類對象是完整的
基類的指針 則指向?qū)ο蟮膭討B(tài)類型可能是基類也可能是派生類
派生類應(yīng)反映與基類的Is A關(guān)系,公有派生類的對象應(yīng)該可以用在任何需要基類對象的地方美尸;類之間Has A則是包含成員的意思冤议。
對于C++面向?qū)ο蟮木幊虂碚f,并不是直接用對象师坎,用的是指針和引用恕酸。