日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

读书笔记之:C++ Primer (第4版)及习题(ch12-ch18) [++++]

發(fā)布時(shí)間:2025/5/22 169 豆豆
生活随笔 收集整理的這篇文章主要介紹了 读书笔记之:C++ Primer (第4版)及习题(ch12-ch18) [++++] 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

第12章 類

1. 類的聲明與定義:前向聲明,不完全類型

2. 從const函數(shù)返回*this

3. 可變數(shù)據(jù)成員mutable

4. 用于const對(duì)象的構(gòu)造函數(shù):構(gòu)造函數(shù)不能聲明為const

5. 構(gòu)造函數(shù)初始化式

構(gòu)造函數(shù)的執(zhí)行分為兩個(gè)階段:初始化階段和普通的計(jì)算階段

6. 構(gòu)造函數(shù)初始化列表

7. 默認(rèn)實(shí)參與構(gòu)造函數(shù)

8. 類通常定義一個(gè)默認(rèn)構(gòu)造函數(shù),不然的話使用起來會(huì)很麻煩。

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

10. 隱式類類型轉(zhuǎn)換:使用explicit來杜絕隱式類類型的轉(zhuǎn)換

11. 類成員的顯式初始化,這種顯式初始化的方式是從C繼承來的

12. static類成員

13. static成員函數(shù)

14. static成員變量

?

第13章 復(fù)制控制

1.C++中的復(fù)制控制

2. 復(fù)制構(gòu)造函數(shù)

3. 合成復(fù)制構(gòu)造函數(shù)

?

4. 禁止復(fù)制

?

5. 智能指針,引用計(jì)數(shù)

智能指針的實(shí)現(xiàn):

View Code #include?<iostream>
#include?<string>
#include?<cstddef>
using?namespace?std;

class?U_Ptr?{
????friend?class?HasPtr;
????int?*ip;
????size_t?use;
????U_Ptr(int?*p):?ip(p),?use(1)?{?}
????~U_Ptr()?{?delete?ip;?}
};

class?HasPtr?{
public:
????HasPtr(int?*p,?int?i):?ptr(new?U_Ptr(p)),?val(i)?{?}

????HasPtr(const?HasPtr?&orig):
???????ptr(orig.ptr),?val(orig.val)?{?++ptr->use;?}
????HasPtr&?operator=(const?HasPtr&);

????~HasPtr()?{?if?(--ptr->use?==?0)?delete?ptr;?}?

????friend?ostream&?operator<<(ostream&,?const?HasPtr&);

????int?*get_ptr()?const?{?return?ptr->ip;?}?
????int?get_int()?const?{?return?val;?}

????void?set_ptr(int?*p)?{?ptr->ip?=?p;?}
????void?set_int(int?i)?{?val?=?i;?}

????int?get_ptr_val()?const?{?return?*ptr->ip;?}?
????void?set_ptr_val(int?i)?{?*ptr->ip?=?i;?}
private:
????U_Ptr?*ptr;????????
????int?val;
};

HasPtr&?HasPtr::operator=(const?HasPtr?&rhs)
{
????++rhs.ptr->use;?????
????if?(--ptr->use?==?0)
?????????delete?ptr;????
????ptr?=?rhs.ptr;??????
????val?=?rhs.val;??????
????return?*this;
}

ostream&?operator<<(ostream?&os,?const?HasPtr?&hp)
{
????os?<<?"*ptr:?"?<<?hp.get_ptr_val()?<<?"\tval:?"?<<?hp.get_int()?<<?endl;
????return?os;
}

int?main()
{
????int?obj?=?0;

????HasPtr?ptr1(&obj,?42);
????HasPtr?ptr2(ptr1);
????cout?<<?"(1)?ptr1:?"?<<?ptr1?<<?endl?<<?"ptr2:?"?<<?ptr2?<<?endl;

????ptr1.set_ptr_val(42);?
????ptr2.get_ptr_val();???

????cout?<<?"(2)?ptr1:?"?<<?ptr1?<<?endl?<<?"ptr2:?"?<<?ptr2?<<?endl;

????ptr1.set_int(0);???
????ptr2.get_int();????
????ptr1.get_int();????

????cout?<<?"(3)?ptr1:?"?<<?ptr1?<<?endl?<<?"ptr2:?"?<<?ptr2?<<?endl;
}

?

第14章 重載操作符與轉(zhuǎn)換

1.可重載的操作符與不可重載的操作符

不要重載具有內(nèi)置含義的操作符

定義為成員函數(shù)或非成員函數(shù)

2. 輸出操作符重載

3. 函數(shù)對(duì)象的函數(shù)適配器:綁定器與求反器

?

4. 轉(zhuǎn)換操作符重載

?

5. 習(xí)題

?

第15章 面向?qū)ο缶幊?

1. 動(dòng)態(tài)綁定virtual,從派生類到基類的轉(zhuǎn)換

2. C++中的多態(tài)性

3. 虛函數(shù)與默認(rèn)實(shí)參

4. 轉(zhuǎn)換與繼承

5. 派生類到基類的轉(zhuǎn)換

5.1 引用轉(zhuǎn)換不同于對(duì)象轉(zhuǎn)換

5.2 派生類對(duì)象對(duì)基類對(duì)象的初始化或賦值:切割

6. 從基類到派生類的轉(zhuǎn)換


7. 復(fù)制控制和繼承

8. 虛析構(gòu)函數(shù)

9. 構(gòu)造函數(shù)和賦值操作符不是虛函數(shù):構(gòu)造函數(shù)不能定義為虛函數(shù),而賦值操作符定義為虛函數(shù)的話會(huì)令人混淆。

10. 構(gòu)造函數(shù)和析構(gòu)函數(shù)中的虛函數(shù)

11. 名字查找與繼承

12. 容器與繼承

容器中如果定義保存基類,那么派生類對(duì)象會(huì)被切割,如果定義為保持派生類,那么會(huì)產(chǎn)生很大問題。

13. 句柄類與繼承

指針型句柄

一個(gè)例子如下:

View Code #include?<iostream>
#include?<iostream>
#include?<string>
#include?<set>
#include?<map>
#include?<utility>
#include?<cstddef>
#include?<stdexcept>
#include?<algorithm>
using?namespace?std;

class?Item_base?{
friend?std::istream&?operator>>(std::istream&,?Item_base&);
friend?std::ostream&?operator<<(std::ostream&,?const?Item_base&);
public:
????virtual?Item_base*?clone()?const?{?return?new?Item_base(*this);?}
public:
????Item_base(const?std::string?&book?=?"",?double?sales_price?=?0.0):
?????????????????????isbn(book),?price(sales_price)?{?}

????std::string?book()?const?{?return?isbn;?}

????virtual?double?net_price(std::size_t?n)?const?{?return?n?*?price;?}

????virtual?~Item_base()?{?}?
private:
????std::string?isbn;???
protected:
????double?price;???????

};

class?Bulk_item?:?public?Item_base?{
public:
????std::pair<size_t,?double>?discount_policy()?const
????????{?return?std::make_pair(min_qty,?discount);?}
????Bulk_item*?clone()?const?
????????{?return?new?Bulk_item(*this);?}
????Bulk_item():?min_qty(0),?discount(0.0)?{?}
????Bulk_item(const?std::string&?book,?double?sales_price,?
??????????????std::size_t?qty?=?0,?double?disc_rate?=?0.0):
?????????????????Item_base(book,?sales_price),?
?????????????????min_qty(qty),?discount(disc_rate)?{?}

????double?net_price(std::size_t?cnt)?const
????{
????????if?(cnt?>=?min_qty)
????????????return?cnt?*?(1?-?discount)?*?price;
????????else
????????????return?cnt?*?price;
????}
private:
????std::size_t?min_qty;???
????double?discount;??????
};

class?Lim_item?:?public?Item_base?{
public:
????Lim_item(const?std::string&?book?=?"",?double?sales_price?=?0.0,
?????????????std::size_t?qty?=?0,?double?disc_rate?=?0.0):
?????????????????Item_base(book,?sales_price),?
?????????????????max_qty(qty),?discount(disc_rate)?{?}

????double?net_price(std::size_t?cnt)?const
????{
????????size_t?discounted?=?min(cnt,?max_qty);
????????size_t?undiscounted?=?cnt?-?discounted;
????????return?discounted?*?(1?-?discount)?*?price?
????????????+?undiscounted?*?price;
????}
private:
????std::size_t?max_qty;???
????double?discount;???????
public:
????Lim_item*?clone()?const?{?return?new?Lim_item(*this);?}
????std::pair<size_t,?double>?discount_policy()?const
????????{?return?std::make_pair(max_qty,?discount);?}
};

class?Sales_item?{
friend?class?Basket;
friend?bool?compare(const?Sales_item&?lhs,const?Sales_item&?rhs);
public:
????Sales_item():?p(0),?use(new?std::size_t(1))?{?}

????Sales_item(const?Item_base&?item):p(item.clone()),?use(new?std::size_t(1))?{?}?

????Sales_item(const?Sales_item?&i):?
??????????????????????p(i.p),?use(i.use)?{?++*use;?}
????~Sales_item()?{?decr_use();?}
????Sales_item&?operator=(const?Sales_item&?rhs)?{
????????++*rhs.use;
????????decr_use();
????????p?=?rhs.p;
????????use?=?rhs.use;
????????return?*this;
????}

????const?Item_base?*operator->()?const?{?if?(p)?return?p;?
????????else?throw?std::logic_error("unbound?Sales_item");?}
????const?Item_base?&operator*()?const?{?if?(p)?return?*p;?
????????else?throw?std::logic_error("unbound?Sales_item");?}
private:
????Item_base?*p;?????????
????std::size_t?*use;?????

????void?decr_use()?
?????????{?if?(--*use?==?0)?{?delete?p;?delete?use;?}?}
};

bool?compare(const?Sales_item&?lhs,const?Sales_item&?rhs){
????return?lhs->book()<rhs->book();
}
class?Basket?{
????typedef?bool?(*Comp)(const?Sales_item&,?const?Sales_item&);
public:
????typedef?std::multiset<Sales_item,?Comp>?set_type;

????typedef?set_type::size_type?size_type;
????typedef?set_type::const_iterator?const_iter;

????void?display(std::ostream&)?const;

????Basket():?items(compare)?{?}???
????void?add_item(const?Sales_item?&item)?
????????????????????????{?items.insert(item);?}
????size_type?size(const?Sales_item?&i)?const
?????????????????????????{?return?items.count(i);?}
????double?total()?const;??
private:
????std::multiset<Sales_item,?Comp>?items;
};

void?Basket::display(ostream?&os)?const
{
????os?<<?"Basket?size:?"?<<?items.size()?<<?endl;

????for?(const_iter?next_item?=?items.begin();?next_item?!=?items.end();
??????????????????next_item?=?items.upper_bound(*next_item))?{
????????os?<<?(*next_item)->book()?<<?"?occurs?"?
???????????<<?items.count(*next_item)?<<?"?times"?
???????????<<?"?for?a?price?of?"?
???????????<<?(*next_item)->net_price(items.count(*next_item))?
???????????<<?endl;
????}
}
void?print_total(ostream?&os,?const?Item_base?&item,?size_t?n)?{
????os?<<?"ISBN:?"?<<?item.book()?//?calls?Item_base::book
???????<<?"\tnumber?sold:?"?<<?n?<<?"\ttotal?price:?"
???????<<?item.net_price(n)?<<?endl;
}
double?Basket::total()?const
{
????double?sum?=?0.0;????//?holds?the?running?total?
????for?(const_iter?iter?=?items.begin();?iter?!=?items.end();
????????????????????iter?=?items.upper_bound(*iter))?{
????????print_total(cout,?*(iter->p),?items.count(*iter));
????????sum?+=?(*iter)->net_price(items.count(*iter));
????}
????return?sum;
}

int?main()
{
????Sales_item?item1(Item_base("123",?45));
????Sales_item?item2(Bulk_item("345",?45,?3,?.15));
????Sales_item?item3(Bulk_item("678",?55,?5,?.25));
????Sales_item?item4(Lim_item("abc",?35,?2,?.10));
????Sales_item?item5(Item_base("def",?35));

????Basket?sale;
????sale.add_item(item1);
cout?<<?"added?first?item"?<<?endl;
????sale.add_item(item1);
????sale.add_item(item1);
????sale.add_item(item1);
????sale.add_item(item2);
????sale.add_item(item2);
????sale.add_item(item2);
????sale.add_item(item2);
????sale.add_item(item2);
????sale.add_item(item2);
????sale.add_item(item2);
????sale.add_item(item2);
????sale.add_item(item3);
????sale.add_item(item3);
????sale.add_item(item3);
????sale.add_item(item3);
????sale.add_item(item3);
????sale.add_item(item3);
????sale.add_item(item3);
????sale.add_item(item3);
????sale.add_item(item4);
????sale.add_item(item4);
????sale.add_item(item4);
????sale.add_item(item4);
????sale.add_item(item4);
????sale.add_item(item4);
????sale.add_item(item5);
????sale.add_item(item5);
cout?<<?"added?last?item"?<<?endl;

????sale.display(cout);
????cout?<<?sale.total()?<<?endl;
{
????//?arguments?are?the?isbn,?price,?minimum?quantity,?and?discount
????Bulk_item?bulk("0-201-82470-1",?50,?5,?.19);
????Basket?sale;
????sale.add_item(Bulk_item("0-201-82470-1",?50,?5,?.19));
????sale.add_item(Bulk_item("0-201-82470-1",?50,?5,?.19));
????sale.add_item(Bulk_item("0-201-82470-1",?50,?5,?.19));
????sale.add_item(Bulk_item("0-201-82470-1",?50,?5,?.19));
????sale.add_item(Bulk_item("0-201-82470-1",?50,?5,?.19));
????sale.add_item(Lim_item("0-201-54848-8",?35,?2,?.10));
????sale.add_item(Lim_item("0-201-54848-8",?35,?2,?.10));
????sale.add_item(Lim_item("0-201-54848-8",?35,?2,?.10));
????double?total?=?sale.total();
????cout?<<?"Total?Sale:?"?<<?total?<<?endl;
}
}

?

?

輸出如下:

?

14. 文本查詢

在原來的基礎(chǔ)上添加了~,& 和| 來進(jìn)行組合查詢。

代碼如下:

View Code #include?<iostream>
#include?<set>
#include?<map>
#include?<vector>
#include?<string>
#include?<algorithm>
#include?<fstream>
using?namespace?std;

class?TextQuery
{
public:
????typedef?string::size_type?str_size;
????typedef?vector<string>::size_type?line_no;
????void?read_file(ifstream&is)?{
????????store_file(is);
????????build_map();
????}
????set<line_no>?run_query(const?string&)const;
????string?text_line(line_no)const;
????void?display_map();
????str_size?size()const?{
????????return?lines_of_text.size();
????}
private:
????void?store_file(ifstream&);
????void?build_map();
private:
????vector<string>?lines_of_text;
????map<string,set<line_no>?>?word_map;
????static?string?sep;
????static?string?cleanup_str(const?string&);
};

string?TextQuery::sep("?\t\n\v\r\f,.`~!@#$%^&*-+=()[]{}<>;:\\/?\'\"|");
void?TextQuery::store_file(ifstream&?is)
{
????string?textline;
????while(getline(is,textline))
????????????lines_of_text.push_back(textline);

}
void?TextQuery::build_map()
{
????string?line;
????string?word;
????for(line_no?num=0;num!=lines_of_text.size();++num)
????{
????????line=lines_of_text[num];
????????string::size_type?pos=0,pos2=0;
????????while((pos2=line.find_first_not_of(sep,pos))
????????????????!=string::npos){
????????????pos=line.find_first_of(sep,pos2);
????????????if(pos!=string::npos){
????????????????word=line.substr(pos2,pos-pos2);
????????????}
????????????else{
????????????????word=line.substr(pos2);
????????????}
????????????word_map[word].insert(num);
????????}
????}
}
set<TextQuery::line_no>TextQuery::run_query(const?string&?query_word)const
{
????map<string,set<line_no>?>::const_iterator?
????????loc=word_map.find(query_word);
????if(loc==word_map.end())
????????return?set<line_no>();
????else
????????return?loc->second;

}
string?TextQuery::text_line(line_no?line)const
{
????if(line<lines_of_text.size())
????????return?lines_of_text[line];
????throw?"line_number?out?of?range";
}
void?TextQuery::display_map()
{
????map<string,set<line_no>?>::iterator?iter=word_map.begin(),
????????iter_end=word_map.end();
????for(;iter!=iter_end;++iter)
????{
????????cout<<"word:?"<<iter->first<<"?{";
????????const?set<line_no>&text_locs=iter->second;
????????set<line_no>::const_iterator?loc_iter=text_locs.begin(),
????????????loc_iter_end=text_locs.end();
????????while(loc_iter!=loc_iter_end)
????????{
????????????cout<<*loc_iter;
????????????if(++loc_iter!=loc_iter_end)
????????????????cout<<",?";
????????}
????????cout<<"}\n";
????}
????cout<<endl;
}
string?TextQuery::cleanup_str(const?string?&word)
{
????string?ret;
????for(string::const_iterator?it=word.begin();it!=word.end();++it)
????{
????????if(!ispunct(*it))
????????????ret+=tolower(*it);
????}
????return?ret;
}
string?make_plural(size_t?ctr,?const?string?&word,const?string?&ending)
{
????return?(ctr?==?1)???word?:?word?+?ending;
}
class?Query_base?{
????friend?class?Query;??
protected:
????typedef?TextQuery::line_no?line_no;
????virtual?~Query_base()?{?}
private:
????virtual?set<line_no>?eval(const?TextQuery&)?const?=?0;?
????virtual?ostream&?display(ostream&?=?cout)?const?=?0;
};

class?Query?{
????friend?Query?operator~(const?Query?&);
????friend?Query?operator|(const?Query&,?const?Query&);
????friend?Query?operator&(const?Query&,?const?Query&);
public:
????Query(const?string&);??
????Query(const?Query?&c):?q(c.q),?use(c.use)?{?++*use;?}
????~Query()?{?decr_use();?}
????Query&?operator=(const?Query&rhs)?{
????????++*rhs.use;?
????????decr_use();?
????????q?=?rhs.q;?
????????use?=?rhs.use;?
????????return?*this;?
????}

????set<TextQuery::line_no>?eval(const?TextQuery?&t)?const?{?return?q->eval(t);?}
????ostream?&display(ostream?&os)?const?{?return?q->display(os);?}
private:
????Query(Query_base?*query):?q(query),?use(new?size_t(1))?{?}
????Query_base?*q;
????size_t?*use;
????void?decr_use()?{?if?(--*use?==?0)?{?delete?q;?delete?use;?}?}
};

ostream&?operator<<(ostream?&os,?const?Query?&q)?{
????return?q.display(os);
}

class?WordQuery:?public?Query_base?{
????friend?class?Query;?
????WordQuery(const?string?&s):?query_word(s)?{?}

????set<line_no>?eval(const?TextQuery?&t)?const
????{?return?t.run_query(query_word);?}
????ostream&?display?(ostream?&os)?const?
????{?return?os?<<?query_word;?}
????string?query_word;????
};

inline?Query::Query(const?string?&s):?q(new?WordQuery(s)),?use(new?size_t(1))?{?}

class?NotQuery:?public?Query_base?{
????friend?Query?operator~(const?Query?&);
????NotQuery(Query?q):?query(q)?{?}

????set<line_no>?eval(const?TextQuery&)?const;
????ostream&?display(ostream?&os)?const
??????????{?return?os?<<?"~("?<<?query?<<?")";?}
????const?Query?query;
};

class?BinaryQuery:?public?Query_base?{
protected:
????BinaryQuery(Query?left,?Query?right,?string?op):?
??????????lhs(left),?rhs(right),?oper(op)?{?}

????ostream&?display(ostream?&os)?const
????{?return?os?<<?"("?<<?lhs??<<?"?"?<<?oper?<<?"?"?<<?rhs?<<?")";?}

????const?Query?lhs,?rhs;???
????const?string?oper;?
};
????
class?AndQuery:?public?BinaryQuery?{
????friend?Query?operator&(const?Query&,?const?Query&);
????AndQuery(Query?left,?Query?right):?
????????????????????????BinaryQuery(left,?right,?"&")?{?}

????set<line_no>?eval(const?TextQuery&)?const;
};

class?OrQuery:?public?BinaryQuery?{
????friend?Query?operator|(const?Query&,?const?Query&);
????OrQuery(Query?left,?Query?right):?
????????????????BinaryQuery(left,?right,?"|")?{?}

????set<line_no>?eval(const?TextQuery&)?const;
};

inline?Query?operator&(const?Query?&lhs,?const?Query?&rhs)
{
????return?new?AndQuery(lhs,?rhs);
}

inline?Query?operator|(const?Query?&lhs,?const?Query?&rhs)
{
????return?new?OrQuery(lhs,?rhs);
}

inline?Query?operator~(const?Query?&oper)
{
????return?new?NotQuery(oper);
}

set<TextQuery::line_no>
NotQuery::eval(const?TextQuery&?file)?const
{
????set<TextQuery::line_no>?has_val?=?query.eval(file);

????set<line_no>?ret_lines;
????for?(TextQuery::line_no?n?=?0;?n?!=?file.size();?++n)
????????if?(has_val.find(n)?==?has_val.end())
????????????ret_lines.insert(n);
????return?ret_lines;
}

set<TextQuery::line_no>
AndQuery::eval(const?TextQuery&?file)?const
{
????set<line_no>?left?=?lhs.eval(file),?
?????????????????right?=?rhs.eval(file);

????set<line_no>?ret_lines;??//?destination?to?hold?results?

????set_intersection(left.begin(),?left.end(),?
??????????????????right.begin(),?right.end(),
??????????????????inserter(ret_lines,?ret_lines.begin()));
????return?ret_lines;
}

set<TextQuery::line_no>
OrQuery::eval(const?TextQuery&?file)?const
{
????set<line_no>?right?=?rhs.eval(file),
?????????????ret_lines?=?lhs.eval(file);??//?destination?to?hold?results

????ret_lines.insert(right.begin(),?right.end());

????return?ret_lines;
}

TextQuery?build_textfile(const?char*?filename)
{
????//?get?a?file?to?read?from?which?user?will?query?words
????ifstream?infile;
????infile.open(filename);
????if?(!infile)?{
????????cerr?<<?"No?input?file!"?<<?endl;
????????return?TextQuery();
????}

????TextQuery?ret;
????ret.read_file(infile);??//?builds?query?map
????return?ret;??//?builds?query?map
}

void?print_results(const?set<TextQuery::line_no>&?locs,?const?TextQuery?&file)
{
????//?report?no?matches
????if?(locs.empty())?{
????????cout?<<?"\nSorry.?There?are?no?entries?for?your?query."?
?????????????<<?"\nTry?again."?<<?endl;
????????return;
????}

????//?if?the?word?was?found,?then?print?count?and?all?occurrences
????set<TextQuery::line_no>::size_type?size?=?locs.size();
????cout?<<?"match?occurs?"?
?????????<<?size?<<?(size?==?1???"?time:"?:?"?times:")?<<?endl;

????//?print?each?line?in?which?the?word?appeared
????set<TextQuery::line_no>::const_iterator?it?=?locs.begin();
????for?(?;?it?!=?locs.end();?++it)?{
????????cout?<<?"\t(line?"
?????????????//?don't?confound?user?with?text?lines?starting?at?0
?????????????<<?(*it)?+?1?<<?")?"
?????????????<<?file.text_line(*it)?<<?endl;
????}
}
string&?trim(string&?s){
????if(s.empty())
????????return?s;
????s.erase(0,s.find_first_not_of('?'));
????s.erase(s.find_last_not_of('?')+1);
????return?s;
}

int?main(int?argc,char**argv){
????TextQuery?file?=?build_textfile(argv[1]);

????string?sought;
????while(true){
????cout?<<?"enter?a?word(s)?to?search?for,?or?q?to?quit:?";
????getline(cin,sought);
????if(!cin||sought=="q")
????????break;
????string::size_type?pos;
????if((pos=sought.find('~'))!=string::npos){

????????sought=sought.substr(pos+1);
????????trim(sought);
????????Query?name(sought);
????????Query?notq?=?~name;
????????const?set<TextQuery::line_no>?locs?=?notq.eval(file);
????????cout?<<?"\nExecuted?Query?for:?"?<<?notq?<<?endl;

????????print_results(locs,?file);
????}
????else?if((pos=sought.find('&'))!=string::npos){
????????string?sought1,?sought2;
????????sought1=sought.substr(0,pos);
????????trim(sought1);
????????sought2=sought.substr(pos+1);
????????trim(sought2);
????????cout<<"sought1="<<sought1<<"?sought2="<<sought2<<endl;
????
????????Query?andq?=?Query(sought1)?&?Query(sought2);
????????set<TextQuery::line_no>?locs?=?andq.eval(file);
????????cout?<<?"\nExecuted?query:?"?<<?andq?<<?endl;
????????print_results(locs,?file);
????
????????locs?=?Query(sought1).eval(file);
????????cout?<<?"\nExecuted?query:?"?<<?Query(sought1)?<<?endl;
????????print_results(locs,?file);
????
????????locs?=?Query(sought2).eval(file);
????????cout?<<?"\nExecuted?query:?"?<<?Query(sought2)?<<?endl;
????????print_results(locs,?file);
????}
????else?if((pos=sought.find('|'))!=string::npos){
????????string?sought1,?sought2;
????????sought1=sought.substr(0,pos);
????????trim(sought1);
????????sought2=sought.substr(pos+1);
????????trim(sought2);
????????cout<<"sought1="<<sought1<<"?sought2="<<sought2<<endl;
????????Query?orq?=?Query(sought1)?|?Query(sought2);
????????cout?<<?"\nExecuting?Query?for:?"?<<?orq?<<?endl;
????????const?set<TextQuery::line_no>?locs?=?orq.eval(file);

????????print_results(locs,?file);

????}
????else{
????????trim(sought);
????????Query?name(sought);
????????const?set<TextQuery::line_no>?locs?=?name.eval(file);
????????cout?<<?"\nExecuted?Query?for:?"?<<?name?<<?endl;

????????print_results(locs,?file);
????}

????}
????return?0;
}

?

簡(jiǎn)單的運(yùn)行結(jié)果如下:

第16章 模板與泛型編程

1. typename與class

只能使用typename來聲明在類內(nèi)部定義的類型成員。

2. 非類型模板參數(shù)

模板形參不一定都是類型。

template?<class?T,size_t?N>
void?array_init(T?(&parm)[N]){
for(size_t?i=0;i!=N;++i)
parm[i]=0;
}

int?main(){
int?x[42];
double?y[10];
array_init(x);
array_init(y);
}

可以使用非類型模板來確定數(shù)組的長(zhǎng)度。

template?<class?T,size_t?N>
size_t?size(T?(&parm)[N]){?????
????return?N;
}

?

3. 模板實(shí)例化

在模板實(shí)參推斷期間確定模板實(shí)參的類型和值。

?

4. 模板推斷過程中涉及的函數(shù)實(shí)參允許的類型轉(zhuǎn)換

?

5.應(yīng)用于非模板形參的常規(guī)轉(zhuǎn)換

6. 模板實(shí)參推斷與函數(shù)指針

可以使用函數(shù)模板對(duì)函數(shù)指針進(jìn)行初始化或賦值,這樣做的時(shí)候,編譯器使用指針的類型實(shí)例化具有適當(dāng)模板實(shí)參的模板版本。

例如,假定有一個(gè)函數(shù)指針指向返回 int 值的函數(shù),該函數(shù)接受兩個(gè)形參,都是 const int 引用,可以用該指針指向 compare 的實(shí)例化

template?<typename?T>?int?compare(const?T&,?const?T&);
?????//?pf1?points?to?the?instantiation?int?compare?(const?int&,?const?int&)
?????int?(*pf1)?(const?int&,?const?int&)?=?compare;

?

pf1 的類型是一個(gè)指針,指向"接受兩個(gè) const int& 類型形參并返回 int 值的函數(shù)",形參的類型決定了 T 的模板實(shí)參的類型,T 的模板實(shí)參為 int 型,指針 pf1 引用的是將 T 綁定到 int 的實(shí)例化。

????獲取函數(shù)模板實(shí)例化的地址的時(shí)候,上下文必須是這樣的:它允許為每個(gè)模板形參確定唯一的類型或值。

如果不能從函數(shù)指針類型確定模板實(shí)參,就會(huì)出錯(cuò)。例如,假定有兩個(gè)名為 func 的函數(shù),每個(gè)函數(shù)接受一個(gè)指向函數(shù)實(shí)參的指針。func 的第一個(gè)版本接受有兩個(gè) const string 引用形參并返回 string 對(duì)象的函數(shù)的指針,func 的第二個(gè)版本接受帶兩個(gè) const int 引用形參并返回 int 值的函數(shù)的指針,不能使用 compare 作為傳給 func 的實(shí)參:

//?overloaded?versions?of?func;?each?take?a?different?function?pointer?type
?????void?func(int(*)?(const?string&,?const?string&));
?????void?func(int(*)?(const?int&,?const?int&));
?????func(compare);?//?error:?which?instantiation?of?compare?

?

?

問題在于,通過查看 func 的形參類型不可能確定模板實(shí)參的唯一類型,對(duì) func 的調(diào)用可以實(shí)例化下列函數(shù)中的任意一個(gè):

compare(const?string&,?const?string&)
?????compare(const?int&,?const?int&)

?

?

因?yàn)椴荒転閭鹘o func 的實(shí)參確定唯一的實(shí)例化,該調(diào)用會(huì)產(chǎn)生一個(gè)編譯時(shí)(或鏈接時(shí))錯(cuò)誤。

7. 函數(shù)模板的顯式實(shí)參

(1) 指定顯式模板實(shí)參

(2)在返回類型中使用類型形參

(3)顯式實(shí)參與函數(shù)模板的指針

?

8. 非類型形參的模板實(shí)參

9. 類模板中的友元聲明
在類模板中可以出現(xiàn)三種友元聲明,每一種都聲明了與一個(gè)或多個(gè)實(shí)體友元關(guān)系:

(1) 普通非模板類或函數(shù)的友元聲明,將友元關(guān)系授予明確指定的類或函數(shù)。

(2) 類模板或函數(shù)模板的友元聲明,授予對(duì)友元所有實(shí)例的訪問權(quán)。

(3) 類模板或函數(shù)模板的特定實(shí)例的訪問權(quán)的友元聲明。

10. 成員模板

任意類(模板或非模板)可以擁有本身為類模板或函數(shù)模板的成員,這種成員稱為成員模板,成員模板不能為虛。

11. 類模板的static成員

?

12. 模板特化

函數(shù)模板特化

?

13. 類模板特化

類模板的部分特化

如果類模板有一個(gè)以上的模板形參,我們也許想要特化某些模板形參而非全部。使用類模板的部分特化可以做到這一點(diǎn):

template?<class?T1,?class?T2>
?????class?some_template?{
?????????//?...
?????};
?????//?partial?specialization:?fixes?T2?as?int?and?allows?T1?to?vary
?????template?<class?T1>
?????class?some_template<T1,?int>?{
?????????//?...
?????};

?

類模板的部分特化本身也是模板。部分特化的定義看來像模板定義,這種定義以關(guān)鍵字 template 開頭,接著是由尖括號(hào)(<>)括住的模板形參表。部分特化的模板形參表是對(duì)應(yīng)的類模板定義形參表的子集。some_template 的部分特化只有一個(gè)名為 T1 的模板類型形參,第二個(gè)模板形參 T2 的實(shí)參已知為 int。部分特化的模板形參表只列出未知模板實(shí)參的那些形參。

部分特化的定義與通用模板的定義完全不會(huì)沖突。部分特化可以具有與通用類模板完全不同的成員集合。類模板成員的通用定義永遠(yuǎn)不會(huì)用來實(shí)例化類模板部分特化的成員。

?

14. 重載與函數(shù)模板

確定重載函數(shù)模板的調(diào)用

可以在不同類型上調(diào)用這些函數(shù):

//?calls?compare(const?T&,?const?T&)?with?T?bound?to?int
?????compare(1,?0);
?????//?calls?compare(U,?U,?V),?with?U?and?V?bound?to?vector<int>::iterator
?????vector<int>?ivec1(10),?ivec2(20);
?????compare(ivec1.begin(),?ivec1.end(),?ivec2.begin());
?????int?ia1[]?=?{0,1,2,3,4,5,6,7,8,9};
?????//?calls?compare(U,?U,?V)?with?U?bound?to?int*
?????
//?and?V?bound?to?vector<int>::iterator
?????compare(ia1,?ia1?+?10,?ivec1.begin());
?????//?calls?the?ordinary?function?taking?const?char*?parameters
?????const?char?const_arr1[]?=?"world",?const_arr2[]?=?"hi";
?????compare(const_arr1,?const_arr2);
?????//?calls?the?ordinary?function?taking?const?char*?parameters
?????char?ch_arr1[]?=?"world",?ch_arr2[]?=?"hi";
?????compare(ch_arr1,?ch_arr2);

?

下面依次介紹每個(gè)調(diào)用。

compare(1, 0):兩個(gè)形參都是 int 類型。候選函數(shù)是第一個(gè)模板將 T 綁定到 int 的實(shí)例化,以及名為 compare 的普通函數(shù)。但該普通函數(shù)不可行——不能將 int 對(duì)象傳給期待 char* 對(duì)象的形參。用 int 實(shí)例化的函數(shù)與該調(diào)用完全匹配,所以選擇它。

compare(ivec1.begin(), ivec1.end(), ivec2.begin())

compare(ia1, ia1 + 10, ivec1.begin()):

這兩個(gè)調(diào)用中,唯一可行的函數(shù)是有三個(gè)形參的模板的實(shí)例化。帶兩個(gè)參數(shù)的模板和普通非模板函數(shù)都不能匹配這兩個(gè)調(diào)用。

compare(const_arr1, const_arr2): 這個(gè)調(diào)用正如我們所期待的,調(diào)用普通函數(shù)。該函數(shù)和將 T 綁定到 const char* 的第一個(gè)模板都是可行的,也都完全匹配。根據(jù)規(guī)則 3b,會(huì)選擇普通函數(shù)。從候選集合中去掉模板實(shí)例,只剩下普通函數(shù)可行。

compare(ch_arr1, ch_arr2):這個(gè)調(diào)用也綁定到普通函數(shù)。候選者是將 T 綁定到 char* 的函數(shù)模板的版本,以及接受 const char* 實(shí)參的普通函數(shù),兩個(gè)函數(shù)都需要稍加轉(zhuǎn)換將數(shù)組 ch_arr1 和 ch_arr2 轉(zhuǎn)換為指針。因?yàn)閮蓚€(gè)函數(shù)一樣匹配,所以普通函數(shù)優(yōu)先于模板版本。

?

第17章 用于大型程序的工具

1. 拋出類類型的異常

異常是通過拋出對(duì)象而引發(fā)的。該對(duì)象的類型決定應(yīng)該激活哪個(gè)處理代碼。被選中的處理代碼是調(diào)用鏈中與該對(duì)象類型匹配且離拋出異常位置最近的那個(gè)。

異常以類似于將實(shí)參傳遞給函數(shù)的方式拋出和捕獲。異常可以是可傳給非引用形參的任意類型的對(duì)象,這意味著必須能夠復(fù)制該類型的對(duì)象。

回憶一下,傳遞數(shù)組或函數(shù)類型實(shí)參的時(shí)候,該實(shí)參自動(dòng)轉(zhuǎn)換為一個(gè)指針。被拋出的對(duì)象將發(fā)生同樣的自動(dòng)轉(zhuǎn)換,因此,不存在數(shù)組或函數(shù)類型的異常。相反。相反,如果拋出一個(gè)數(shù)組,被拋出的對(duì)象轉(zhuǎn)換為指向數(shù)組首元素的指針,類似地,如果拋出一個(gè)函數(shù),函數(shù)被轉(zhuǎn)換為指向該函數(shù)的指針第 7.9 節(jié)。

執(zhí)行 throw 的時(shí)候,不會(huì)執(zhí)行跟在 throw 后面的語句,而是將控制從 throw 轉(zhuǎn)移到匹配的 catch,該 catch 可以是同一函數(shù)中局部的 catch,也可以在直接或間接調(diào)用發(fā)生異常的函數(shù)的另一個(gè)函數(shù)中。控制從一個(gè)地方傳到另一地方,這有兩個(gè)重要含義:

1. 沿著調(diào)用鏈的函數(shù)提早退出。第 17.1.2 節(jié)將討論函數(shù)因異常而退出時(shí)會(huì)發(fā)生什么。

2. 一般而言,在處理異常的時(shí)候,拋出異常的塊中的局部存儲(chǔ)不存在了。

因?yàn)樵谔幚懋惓5臅r(shí)候會(huì)釋放局部存儲(chǔ),所以被拋出的對(duì)象就不能再局部存儲(chǔ),而是用 throw 表達(dá)式初始化一個(gè)稱為異常對(duì)象的特殊對(duì)象。異常對(duì)象由編譯器管理,而且保證駐留在可能被激活的任意 catch 都可以訪問的空間。這個(gè)對(duì)象由 throw 創(chuàng)建,并被初始化為被拋出的表達(dá)式的副本。異常對(duì)象將傳給對(duì)應(yīng)的 catch,并且在完全處理了異常之后撤銷。

異常對(duì)象通過復(fù)制被拋出表達(dá)式的結(jié)果創(chuàng)建,該結(jié)果必須是可以復(fù)制的類型

?

2. 異常對(duì)象與繼承

當(dāng)拋出一個(gè)表達(dá)式的時(shí)候,被拋出對(duì)象的靜態(tài)編譯時(shí)類型將決定異常對(duì)象的類型。

通常,使用靜態(tài)類型拋出對(duì)象不成問題。當(dāng)拋出一個(gè)異常的時(shí)候,通常在拋出點(diǎn)構(gòu)造將拋出的對(duì)象,該對(duì)象表示出了什么問題,所以我們知道確切的異常類型。

3. 異常與指針

用拋出表達(dá)式拋出靜態(tài)類型時(shí),比較麻煩的一種情況是,在拋出中對(duì)指針解引用。對(duì)指針解引用的結(jié)果是一個(gè)對(duì)象,其類型與指針的類型匹配。如果指針指向繼承層次中的一種類型,指針?biāo)笇?duì)象的類型就有可能與指針的類型不同。無論對(duì)象的實(shí)際類型是什么,異常對(duì)象的類型都與指針的靜態(tài)類型相匹配。如果該指針是一個(gè)指向派生類對(duì)象的基類類型指針,則那個(gè)對(duì)象將被分割,只拋出基類部分。

?

如果拋出指針本身,可能會(huì)引發(fā)比分割對(duì)象更嚴(yán)重的問題。具體而言,拋出指向局部對(duì)象的指針總是錯(cuò)誤的,其理由與從函數(shù)返回指向局部對(duì)象的指針是錯(cuò)誤的一樣。拋出指針的時(shí)候,必須確定進(jìn)入處理代碼時(shí)指針?biāo)赶虻膶?duì)象存在。

?

如果拋出指向局部對(duì)象的指針,而且處理代碼在另一函數(shù)中,則執(zhí)行處理代碼時(shí)指針?biāo)赶虻膶?duì)象將不再存在。即使處理代碼在同一函數(shù)中,也必須確信指針?biāo)赶虻膶?duì)象在 catch 處存在。如果指針指向某個(gè)在 catch 之前退出的塊中的對(duì)象,那么,將在 catch 之前撤銷該局部對(duì)象。

?

4. 棧展開Stack Unwinding

拋出異常的時(shí)候,將暫停當(dāng)前函數(shù)的執(zhí)行,開始查找匹配的 catch 子句。首先檢查 throw 本身是否在 try 塊內(nèi)部,如果是,檢查與該 catch 相關(guān)的 catch 子句,看是否其中之一與拋出對(duì)象相匹配。如果找到匹配的 catch,就處理異常;如果找不到,就退出當(dāng)前函數(shù)(釋放當(dāng)前函數(shù)的內(nèi)在并撤銷局部對(duì)象),并且繼續(xù)在調(diào)用函數(shù)中查找。

如果對(duì)拋出異常的函數(shù)的調(diào)用是在 try 塊中,則檢查與該 try 相關(guān)的 catch 子句。如果找到匹配的 catch,就處理異常;如果找不到匹配的 catch,調(diào)用函數(shù)也退出,并且繼續(xù)在調(diào)用這個(gè)函數(shù)的函數(shù)中查找。

這個(gè)過程,稱之為棧展開(stack unwinding),沿嵌套函數(shù)調(diào)用鏈繼續(xù)向上,直到為異常找到一個(gè) catch 子句。只要找到能夠處理異常的 catch 子句,就進(jìn)入該 catch 子句,并在該處理代碼中繼續(xù)執(zhí)行。當(dāng) catch 結(jié)束的時(shí)候,在緊接在與該 try 塊相關(guān)的最后一個(gè) catch 子句之后的點(diǎn)繼續(xù)執(zhí)行。

(1)為局部對(duì)象調(diào)用析構(gòu)函數(shù)

棧展開期間,提早退出包含 throw 的函數(shù)和調(diào)用鏈中可能的其他函數(shù)。一般而言,這些函數(shù)已經(jīng)創(chuàng)建了可以在退出函數(shù)時(shí)撤銷的局部對(duì)象。因異常而退出函數(shù)時(shí),編譯器保證適當(dāng)?shù)爻蜂N局部對(duì)象。每個(gè)函數(shù)退出的時(shí)候,它的局部存儲(chǔ)都被釋放,在釋放內(nèi)存之前,撤銷在異常發(fā)生之前創(chuàng)建的所有對(duì)象。如果局部對(duì)象是類類型的,就自動(dòng)調(diào)用該對(duì)象的析構(gòu)函數(shù)。通常,編譯器不撤銷內(nèi)置類型的對(duì)象。

棧展開期間,釋放局部對(duì)象所用的內(nèi)存并運(yùn)行類類型局部對(duì)象的析構(gòu)函數(shù)。

如果一個(gè)塊直接分配資源,而且在釋放資源之前發(fā)生異常,在棧展開期間將不會(huì)釋放該資源。例如,一個(gè)塊可以通過調(diào)用 new 動(dòng)態(tài)分配內(nèi)存,如果該塊因異常而退出,編譯器不會(huì)刪除該指針,已分配的內(nèi)在將不會(huì)釋放。

由類類型對(duì)象分配的資源一般會(huì)被適當(dāng)?shù)蒯尫拧_\(yùn)行局部對(duì)象的析構(gòu)函數(shù),由類類型對(duì)象分配的資源通常由它們的析構(gòu)函數(shù)釋放。第 17.1.8 節(jié)說明面對(duì)異常使用類管理資源分配的編程技術(shù)

(2)析構(gòu)函數(shù)應(yīng)該從不拋出異常

棧展開期間會(huì)經(jīng)常執(zhí)行析構(gòu)函數(shù)。在執(zhí)行析構(gòu)函數(shù)的時(shí)候,已經(jīng)引發(fā)了異常但還沒有處理它。如果在這個(gè)過程中析構(gòu)函數(shù)本身拋出新的異常,又會(huì)發(fā)生什么呢?新的異常應(yīng)該取代仍未處理的早先的異常嗎?應(yīng)該忽略析構(gòu)函數(shù)中的異常嗎?

答案是:在為某個(gè)異常進(jìn)行棧展開的時(shí)候,析構(gòu)函數(shù)如果又拋出自己的未經(jīng)處理的另一個(gè)異常,將會(huì)導(dǎo)致調(diào)用標(biāo)準(zhǔn)庫 terminate 函數(shù)。一般而言,terminate 函數(shù)將調(diào)用 abort 函數(shù),強(qiáng)制從整個(gè)程序非正常退出。

因?yàn)?terminate 函數(shù)結(jié)束程序,所以析構(gòu)函數(shù)做任何可能導(dǎo)致異常的事情通常都是非常糟糕的主意。在實(shí)踐中,因?yàn)槲鰳?gòu)函數(shù)釋放資源,所以它不太可能拋出異常。標(biāo)準(zhǔn)庫類型都保證它們的析構(gòu)函數(shù)不會(huì)引發(fā)異常。

(3)異常與構(gòu)造函數(shù)

與析構(gòu)函數(shù)不同,構(gòu)造函數(shù)內(nèi)部所做的事情經(jīng)常會(huì)拋出異常。如果在構(gòu)造函數(shù)對(duì)象的時(shí)候發(fā)生異常,則該對(duì)象可能只是部分被構(gòu)造,它的一些成員可能已經(jīng)初始化,而另一些成員在異常發(fā)生之前還沒有初始化。即使對(duì)象只是部分被構(gòu)造了,也要保證將會(huì)適當(dāng)?shù)爻蜂N已構(gòu)造的成員。

類似地,在初始化數(shù)組或其他容器類型的元素的時(shí)候,也可能發(fā)生異常,同樣,也要保證將會(huì)適當(dāng)?shù)爻蜂N已構(gòu)造的元素。

(4)未捕獲的異常終止程序

不能不處理異常。異常是足夠重要的、使程序不能繼續(xù)正常執(zhí)行的事件。如果找不到匹配的 catch,程序就調(diào)用庫函數(shù) terminate。

?

5. 捕獲異常

catch 子句中的異常說明符看起來像只包含一個(gè)形參的形參表,異常說明符是在其后跟一個(gè)(可選)形參名的類型名。

說明符的類型決定了處理代碼能夠捕獲的異常種類。類型必須是完全類型,即必須是內(nèi)置類型或者是已經(jīng)定義的程序員自定義類型。類型的前向聲明不行。

?

當(dāng) catch 為了處理異常只需要了解異常的類型的時(shí)候,異常說明符可以省略形參名;如果處理代碼需要已發(fā)生異常的類型之外的信息,則異常說明符就包含形參名,catch 使用這個(gè)名字訪問異常對(duì)象。

(1)查找匹配的處理代碼

在查找匹配的 catch 期間,找到的 catch 不必是與異常最匹配的那個(gè) catch,相反,將選中第一個(gè)找到的可以處理該異常的 catch。因此,在 catch 子句列表中,最特殊的 catch 必須最先出現(xiàn)。

異常與 catch 異常說明符匹配的規(guī)則比匹配實(shí)參和形參類型的規(guī)則更嚴(yán)格,大多數(shù)轉(zhuǎn)換都不允許——除下面幾種可能的區(qū)別之外,異常的類型與 catch 說明符的類型必須完全匹配:

*允許從非 const 到 const 的轉(zhuǎn)換。也就是說,非 const 對(duì)象的 throw 可以與指定接受 const 引用的 catch 匹配。

*允許從派生類型型到基類類型的轉(zhuǎn)換。

*將數(shù)組轉(zhuǎn)換為指向數(shù)組類型的指針,將函數(shù)轉(zhuǎn)換為指向函數(shù)類型的適當(dāng)指針。

在查找匹配 catch 的時(shí)候,不允許其他轉(zhuǎn)換。具體而言,既不允許標(biāo)準(zhǔn)算術(shù)轉(zhuǎn)換,也不允許為類類型定義的轉(zhuǎn)換。

(2)異常說明符

進(jìn)入 catch 的時(shí)候,用異常對(duì)象初始化 catch 的形參。像函數(shù)形參一樣,異常說明符類型可以是引用。異常對(duì)象本身是被拋出對(duì)象的副本。是否再次將異常對(duì)象復(fù)制到 catch 位置取決于異常說明符類型。

如果說明符不是引用,就將異常對(duì)象復(fù)制到 catch 形參中,catch 操作異常對(duì)象的副本,對(duì)形參所做的任何改變都只作用于副本,不會(huì)作用于異常對(duì)象本身。如果說明符是引用,則像引用形參一樣,不存在單獨(dú)的 catch 對(duì)象,catch 形參只是異常對(duì)象的另一名字。對(duì) catch 形參所做的改變作用于異常對(duì)象。

(3)異常說明符與繼承

像形參聲明一樣,基類的異常說明符可以用于捕獲派生類型的異常對(duì)象,而且,異常說明符的靜態(tài)類型決定 catch 子句可以執(zhí)行的動(dòng)作。如果被拋出的異常對(duì)象是派生類類型的,但由接受基類類型的 catch 處理,那么,catch 不能使用派生類特有的任何成員。

通常,如果 catch 子句處理因繼承而相關(guān)的類型的異常,它就應(yīng)該將自己的形參定義為引用。

如果 catch 形參是引用類型,catch 對(duì)象就直接訪問異常對(duì)象,catch 對(duì)象的靜態(tài)類型可以與 catch 對(duì)象所引用的異常對(duì)象的動(dòng)態(tài)類型不同。如果異常說明符不是引用,則 catch 對(duì)象是異常對(duì)象的副本,如果 catch 對(duì)象是基類類型對(duì)象而異常對(duì)象是派生類型的,就將異常對(duì)象分割(第 15.3.1 節(jié))為它的基類子對(duì)象。

而且,正如第 15.2.4 節(jié)所介紹的,對(duì)象(相對(duì)于引用)不是多態(tài)的。當(dāng)通過對(duì)象而不是引用使用虛函數(shù)的時(shí)候,對(duì)象的靜態(tài)類型和動(dòng)態(tài)類型相同,函數(shù)是虛函數(shù)也一樣。只有通過引用或指針調(diào)用時(shí)才發(fā)生動(dòng)態(tài)綁定,通過對(duì)象調(diào)用不進(jìn)行動(dòng)態(tài)綁定。

(4)catch子句的次序必須反映類型層次

將異常類型組織成類層次的時(shí)候,用戶可以選擇應(yīng)用程序處理異常的粒度級(jí)別。例如,只希望清除并退出的應(yīng)用程序可以定義一個(gè) try 塊,該 try 塊包圍 main 函數(shù)中帶有如下 catch 代碼:

catch(exception?&e)?{
????????//?do?cleanup
????????
//?print?a?message
????????cerr?<<?"Exiting:?"?<<?e.what()?<<?endl;
????????size_t?status_indicator?=?42;??//?set?and?return?an
????????return(status_indicator);??????//?error?indicator
????}

有更嚴(yán)格實(shí)時(shí)需求的程序可能需要更好的異常控制,這樣的應(yīng)用程序?qū)⑶宄龑?dǎo)致異常的一切并繼續(xù)執(zhí)行。

因?yàn)?catch 子句按出現(xiàn)次序匹配,所以使用來自繼承層次的異常的程序必須將它們的 catch 子句排序,以便派生類型的處理代碼出現(xiàn)在其基類類型的 catch 之前。

?

6. 重新拋出

一般而言,catch 可以改變它的形參。在改變它的形參之后,如果 catch 重新拋出異常,那么,只有當(dāng)異常說明符是引用的時(shí)候,才會(huì)傳播那些改變。

catch?(my_error?&eObj)?{????????//?specifier?is?a?reference?type
????????eObj.status?=?severeErr;????//?modifies?the?exception?object
????????throw;?//?the?status?member?of?the?exception?object?is?severeErr
????}?catch?(other_error?eObj)?{????//?specifier?is?a?nonreference?type
????????eObj.status?=?badErr;???????//?modifies?local?copy?only
????????throw;?//?the?status?member?of?the?exception?rethrown?is?unchanged
????}

?

?

7. 捕獲所有異常的代碼

即使函數(shù)不能處理被拋出的異常,它也可能想要在隨拋出異常退出之前執(zhí)行一些動(dòng)作。除了為每個(gè)可能的異常提供特定 catch 子句之外,因?yàn)椴豢赡苤揽赡鼙粧伋龅乃挟惓?#xff0c;所以可以使用捕獲所有異常 catch 子句的。捕獲所有異常的 catch 子句形式為 (...)。例如:

//?matches?any?exception?that?might?be?thrown
?????catch?(...)?{
?????????//?place?our?code?here
?????}

?

8. 標(biāo)準(zhǔn)異常類

9. 自動(dòng)資源釋放

用類管理資源分配。

對(duì)析構(gòu)函數(shù)的運(yùn)行導(dǎo)致一個(gè)重要的編程技術(shù)的出現(xiàn),它使程序更為異常安全的。異常安全的意味著,即使發(fā)生異常,程序也能正確操作。在這種情況下,"安全"來自于保證"如果發(fā)生異常,被分配的任何資源都適當(dāng)?shù)蒯尫?#34;。

通過定義一個(gè)類來封閉資源的分配和釋放,可以保證正確釋放資源。這一技術(shù)常稱為"資源分配即初始化",簡(jiǎn)稱 RAII。

應(yīng)該設(shè)計(jì)資源管理類,以便構(gòu)造函數(shù)分配資源而析構(gòu)函數(shù)釋放資源。想要分配資源的時(shí)候,就定義該類類型的對(duì)象。如果不發(fā)生異常,就在獲得資源的對(duì)象超出作用域的進(jìn)修釋放資源。更為重要的是,如果在創(chuàng)建了對(duì)象之后但在它超出作用域之前發(fā)生異常,那么,編譯器保證撤銷該對(duì)象,作為展開定義對(duì)象的作用域的一部分。

?

10. auto_ptr類

位于頭文件memory中,智能指針

auto_ptr 只能用于管理從 new 返回的一個(gè)對(duì)象,它不能管理動(dòng)態(tài)分配的數(shù)組。

正如我們所見,當(dāng) auto_ptr 被復(fù)制或賦值的時(shí)候,有不尋常的行為,因此,不能將 auto_ptrs 存儲(chǔ)在標(biāo)準(zhǔn)庫容器類型中。

auto_ptr 對(duì)象只能保存一個(gè)指向?qū)ο蟮闹羔?#xff0c;并且不能用于指向動(dòng)態(tài)分配的數(shù)組,使用 auto_ptr 對(duì)象指向動(dòng)態(tài)分配的數(shù)組會(huì)導(dǎo)致未定義的運(yùn)行時(shí)行為。

每個(gè) auto_ptr 對(duì)象綁定到一個(gè)對(duì)象或者指向一個(gè)對(duì)象。當(dāng) auto_ptr 對(duì)象指向一個(gè)對(duì)象的時(shí)候,可以說它"擁有"該對(duì)象。當(dāng) auto_ptr 對(duì)象超出作用域或者另外撤銷的時(shí)候,就自動(dòng)回收 auto_ptr 所指向的動(dòng)態(tài)分配對(duì)象。

(1)為異常安全的內(nèi)存分配使用 auto_ptr

如果通過常規(guī)指針分配內(nèi)在,而且在執(zhí)行 delete 之前發(fā)生異常,就不會(huì)自動(dòng)釋放該內(nèi)存:

void?f()
?????{
????????int?*ip?=?new?int(42);?????//?dynamically?allocate?a?new?object
????????
//?code?that?throws?an?exception?that?is?not?caught?inside?f
????????delete?ip;?????????????????//?return?the?memory?before?exiting
?????}

?

如果在 new 和 delete 之間發(fā)生異常,并且該異常不被局部捕獲,就不會(huì)執(zhí)行 delete,則永不回收該內(nèi)存。

如果使用一個(gè) auto_ptr 對(duì)象來代替,將會(huì)自動(dòng)釋放內(nèi)存,即使提早退出這個(gè)塊也是這樣:

void?f()
?????{
????????auto_ptr<int>?ap(new?int(42));?//?allocate?a?new?object
????????
//?code?that?throws?an?exception?that?is?not?caught?inside?f
?????}?????//?auto_ptr?freed?automatically?when?function?ends

?

在這個(gè)例子中,編譯器保證在展開棧越過 f 之前運(yùn)行 ap 的析構(gòu)函數(shù)。

?

(2)auto_ptr 是可以保存任何類型指針的模板

auto_ptr 類是接受單個(gè)類型形參的模板,該類型指定 auto_ptr 可以綁定的對(duì)象的類型,因此,可以創(chuàng)建任何類型的 auto_ptrs:

auto_ptr<string>?ap1(new?string("Brontosaurus"));

?

(3)將 auto_ptr 綁定到指針

在最常見的情況下,將 auto_ptr 對(duì)象初始化為由 new 表達(dá)式返回的對(duì)象的地址:

auto_ptr<int>?pi(new?int(1024));

這個(gè)語句將 pi 初始化為由 new 表達(dá)式創(chuàng)建的對(duì)象的地址,這個(gè) new 表達(dá)式將對(duì)象初始化為 1024。

接受指針的構(gòu)造函數(shù)為 explicit(第 12.4.4 節(jié))構(gòu)造函數(shù),所以必須使用初始化的直接形式來創(chuàng)建 auto_ptr 對(duì)象:

//?error:?constructor?that?takes?a?pointer?is?explicit?and?can't?be?used?implicitly
auto_ptr<int>?pi?=?new?int(1024);
auto_ptr<int>?pi(new?int(1024));?//?ok:?uses?direct?initialization

pi 所指的由 new 表達(dá)式創(chuàng)建的對(duì)象在超出作用域時(shí)自動(dòng)刪除。如果 pi 是局部對(duì)象,pi 所指對(duì)象在定義 pi 的塊的末尾刪除;如果發(fā)生異常,則 pi 也超出作用域,析構(gòu)函數(shù)將自動(dòng)運(yùn)行 pi 的析構(gòu)函數(shù)作為異常處理的一部分;如果 pi 是全局對(duì)象,就在程序末尾刪除 pi 引用的對(duì)象。

(4)使用 auto_ptr 對(duì)象

auto_ptr 類定義了解引用操作符(*)和箭頭操作符(->)的重載版本(第 14.6 節(jié)),因?yàn)?auto_ptr 定義了這些操作符,所以可以用類似于使用內(nèi)置指針的方式使用 auto_ptr 對(duì)象:

?

//?normal?pointer?operations?for?dereference?and?arrow
*ap1?=?"TRex";?//?assigns?a?new?value?to?the?object?to?which?ap1?points
string?s?=?*ap1;?//?initializes?s?as?a?copy?of?the?object?to?which?ap1?points
if?(ap1->empty())?//?runs?empty?on?the?string?to?which?ap1?points

auto_ptr 的主要目的,在保證自動(dòng)刪除 auto_ptr 對(duì)象引用的對(duì)象的同時(shí),支持普通指針式行為。正如我們所見,自動(dòng)刪除該對(duì)象這一事實(shí)導(dǎo)致在怎樣復(fù)制和訪問它們的地址值方面,auto_ptrs 與普通指針明顯不同。

(5)auto_ptr 對(duì)象的復(fù)制和賦值是破壞性操作

auto_ptr 和內(nèi)置指針對(duì)待復(fù)制和賦值有非常關(guān)鍵的重要區(qū)別。當(dāng)復(fù)制 auto_ptr 對(duì)象或者將它的值賦給其他 auto_ptr 對(duì)象的時(shí)候,將基礎(chǔ)對(duì)象的所有權(quán)從原來的 auto_ptr 對(duì)象轉(zhuǎn)給副本,原來的 auto_ptr 對(duì)象重置為未綁定狀態(tài)。

?

(6)賦值刪除左操作數(shù)指向的對(duì)象

除了將所有權(quán)從右操作數(shù)轉(zhuǎn)給左操作數(shù)之外,賦值還刪除左操作數(shù)原來指向的對(duì)象——假如兩個(gè)對(duì)象不同。通常自身賦值沒有效果。

auto_ptr<string>?ap3(new?string("Pterodactyl"));
//?object?pointed?to?by?ap3?is?deleted?and?ownership?transferred?from?ap2?to?ap3;
ap3?=?ap2;?//?after?the?assignment,?ap2?is?unbound

因?yàn)閺?fù)制和賦值是破壞性操作,所以auto_ptrs不能將 auto_ptr 對(duì)象存儲(chǔ)在標(biāo)準(zhǔn)容器中。標(biāo)準(zhǔn)庫的容器類要求在復(fù)制或賦值之后兩個(gè)對(duì)象相等,auto_ptr 不滿足這一要求,如果將 ap2 賦給 ap1,則在賦值之后 ap1 != ap2,復(fù)制也類似。

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

如果不給定初始式,auto_ptr 對(duì)象是未綁定的,它不指向?qū)ο?#xff1a;

auto_ptr<int>?p_auto;?//?p_autodoesn't?refer?to?any?object

?

默認(rèn)情況下,auto_ptr 的內(nèi)部指針值置為 0。對(duì)未綁定的 auto_ptr 對(duì)象解引用,其效果與對(duì)未綁定的指針解引用相同——程序出錯(cuò)并且沒有定義會(huì)發(fā)生什么:

*p_auto?=?1024;?//?error:?dereference?auto_ptr?that?doesn't?point?to?an?object

?

(8)測(cè)試 auto_ptr 對(duì)象

auto_ptr 類型沒有定義到可用作條件的類型的轉(zhuǎn)換,相反,要測(cè)試 auto_ptr 對(duì)象,必須使用它的 get 成員,該成員返回包含在 auto_ptr 對(duì)象中的基礎(chǔ)指針:

//?revised?test?to?guarantee?p_auto?refers?to?an?object
if?(p_auto.get())
*p_auto?=?1024;

使用 get 成員初始化其他 auto_ptr 對(duì)象違反 auto_ptr 類設(shè)計(jì)原則:在任意時(shí)刻只有一個(gè) auto_ptrs 對(duì)象保存給定指針,如果兩個(gè) auto_ptrs 對(duì)象保存相同的指針,該指針就會(huì)被 delete 兩次。

(9)reset 操作

auto_ptr 對(duì)象與內(nèi)置指針的另一個(gè)區(qū)別是,不能直接將一個(gè)地址(或者其他指針)賦給 auto_ptr 對(duì)象:

p_auto?=?new?int(1024);?//?error:?cannot?assign?a?pointer?to?an?auto_ptr

?

相反,必須調(diào)用 reset 函數(shù)來改變指針:

//?revised?test?to?guarantee?p_auto?refers?to?an?object
if?(p_auto.get())
*p_auto?=?1024;
else
//?reset?p_auto?to?a?new?object
p_auto.reset(new?int(1024));

要復(fù)位 auto_ptr 對(duì)象,可以將 0 傳給 reset 函數(shù)。

?

11. auto_ptr的缺陷

auto_ptr 類模板為處理動(dòng)態(tài)分配的內(nèi)存提供了安全性和便利性的尺度。要正確地使用 auto_ptr 類,必須堅(jiān)持該類強(qiáng)加的下列限制:

1.不要使用 auto_ptr 對(duì)象保存指向靜態(tài)分配對(duì)象的指針,否則,當(dāng) auto_ptr 對(duì)象本身被撤銷的時(shí)候,它將試圖刪除指向非動(dòng)態(tài)分配對(duì)象的指針,導(dǎo)致未定義的行為。

2.永遠(yuǎn)不要使用兩個(gè) auto_ptr 對(duì)象指向同一對(duì)象,導(dǎo)致這個(gè)錯(cuò)誤的一種明顯方式是,使用同一指針來初始化或者 reset 兩個(gè)不同的 auto_ptr 對(duì)象。另一種導(dǎo)致這個(gè)錯(cuò)誤的微妙方式可能是,使用一個(gè) auto_ptr 對(duì)象的 get 函數(shù)的結(jié)果來初始化或者 reset 另一個(gè) auto_ptr 對(duì)象。

3.不要使用 auto_ptr 對(duì)象保存指向動(dòng)態(tài)分配數(shù)組的指針。當(dāng) auto_ptr 對(duì)象被刪除的時(shí)候,它只釋放一個(gè)對(duì)象——它使用普通 delete 操作符,而不用數(shù)組的 delete [] 操作符。

4.不要將 auto_ptr 對(duì)象存儲(chǔ)在容器中。容器要求所保存的類型定義復(fù)制和賦值操作符,使它們表現(xiàn)得類似于內(nèi)置類型的操作符:在復(fù)制(或者賦值)之后,兩個(gè)對(duì)象必須具有相同值,auto_ptr 類不滿足這個(gè)要求。

?

12. 異常說明
異常說明跟在函數(shù)形參表之后。一個(gè)異常說明在關(guān)鍵字 throw 之后跟著一個(gè)(可能為空的)由圓括號(hào)括住的異常類型列表。

空說明列表指出函數(shù)不拋出任何異常:

void no_problem() throw();

異常說明是函數(shù)接口的一部分,函數(shù)定義以及該函數(shù)的任意聲明必須具有相同的異常說明。

如果一個(gè)函數(shù)聲明沒有指定異常說明,則該函數(shù)可以拋出任意類型的異常。

(1)違反異常說明

如果函數(shù)拋出了沒有在其異常說明中列出的異常,就調(diào)用標(biāo)準(zhǔn)庫函數(shù) unexpected。默認(rèn)情況下,unexpected 函數(shù)調(diào)用 terminate 函數(shù),terminate 函數(shù)一般會(huì)終止程序。

(2)確定函數(shù)不拋出異常

異常說服有用的一種重要情況是,如果函數(shù)可以保證不會(huì)拋出任何異常。

確定函數(shù)將不拋出任何異常,對(duì)函數(shù)的用戶和編譯器都有所幫助:知道函數(shù)不拋出異常會(huì)簡(jiǎn)化編寫調(diào)用該函數(shù)的異常安全的代碼的工作,我們可以知道在調(diào)用函數(shù)時(shí)不必?fù)?dān)心異常,而且,如果編譯器知道不會(huì)拋出異常,它就可以執(zhí)行被可能拋出異常的代碼所抑制的優(yōu)化。

(3)異常說明與成員函數(shù)

像非成員函數(shù)一樣,成員函數(shù)聲明的異常說明跟在函數(shù)形參表之后。例如,C++ 標(biāo)準(zhǔn)庫中的 bad_alloc 類定義為所有成員都有空異常說明,這些成員承諾不拋出異常:

//?ilustrative?definition?of?library?bad_alloc?class
?????class?bad_alloc?:?public?exception?{
?????public:
?????????bad_alloc()?throw();
?????????bad_alloc(const?bad_alloc?&)?throw();
?????????bad_alloc?&?operator=(const
?????????bad_alloc?&)?throw();
?????????virtual?~bad_alloc()?throw();
?????????virtual?const?char*?what()?const?throw();
?????};

?

注意,在 const 成員函數(shù)聲明中,異常說明跟在 const 限定符之后。

(4)異常說明與虛函數(shù)

基類中虛函數(shù)的異常說明,可以與派生類中對(duì)應(yīng)虛函數(shù)的異常說明不同。但是,派生類虛函數(shù)的異常說明必須與對(duì)應(yīng)基類虛函數(shù)的異常說明同樣嚴(yán)格,或者比后者更受限。

這個(gè)限制保證,當(dāng)使用指向基類類型的指針調(diào)用派生類虛函數(shù)的時(shí)候,派生類的異常說明不會(huì)增加新的可拋出異常。例如:

class?Base?{
?????public:
?????????virtual?double?f1(double)?throw?();
?????????virtual?int?f2(int)?throw?(std::logic_error);
?????????virtual?std::string?f3()?throw
???????????????(std::logic_error,?std::runtime_error);
?????};
?????class?Derived?:?public?Base?{
?????public:
?????????//?error:?exception?specification?is?less?restrictive?than?Base::f1's
?????????double?f1(double)?throw?(std::underflow_error);
?????????//?ok:?same?exception?specification?as?Base::f2
?????????int?f2(int)?throw?(std::logic_error);
?????????//?ok:?Derived?f3?is?more?restrictive
?????????std::string?f3()?throw?();
?????};

?

派生類中 f1 的聲明是錯(cuò)誤的,因?yàn)樗漠惓Uf明在基類 f1 版本列出的異常中增加了一個(gè)異常。派生類不能在異常說明列表中增加異常,原因在于,繼承層次的用戶應(yīng)該能夠編寫依賴于該說明列表的代碼。如果通過基類指針或引用進(jìn)行函數(shù)調(diào)用,那么,這些類的用戶所涉及的應(yīng)該只是在基類中指定的異常。

通過派生類拋出的異常限制為由基類所列出的那些,在編寫代碼時(shí)就可以知道必須處理哪些異常。代碼可以依賴于這樣一個(gè)事實(shí):基類中的異常列表是虛函數(shù)的派生類版本可以拋出的異常列表的超集。例如,當(dāng)調(diào)用 f3 的時(shí)候,我們知道只需要處理 logic_error 或 runtime_error:

//?guarantees?not?to?throw?exceptions
?????void?compute(Base?*pb)?throw()
?????{
?????????try?{
?????????????//?may?throw?exception?of?type?std::logic_error
?????????????
//?or?std::runtime_error
?????????????pb->f3();
?????????}?catch?(const?logic_error?&le)???{?/*?...?*/?}
???????????catch?(const?runtime_error?&re)?{?/*?...?*/?}
?????}

?

(5)函數(shù)指針異常說明

異常說明是函數(shù)類型的一部分。這樣,也可以在函數(shù)指針的定義中提供異常說明:

void (*pf)(int) throw(runtime_error);

這個(gè)聲明是說,pf 指向接受 int 值的函數(shù),該函數(shù)返回 void 對(duì)象,該函數(shù)只能拋出 runtime_error 類型的異常。如果不提供異常說明,該指針就可以指向能夠拋出任意類型異常的具有匹配類型的函數(shù)。

在用另一指針初始化帶異常說明的函數(shù)的指針,或者將后者賦值給函數(shù)地址的時(shí)候,兩個(gè)指針的異常說明不必相同,但是,源指針的異常說明必須至少與目標(biāo)指針的一樣嚴(yán)格。

void?recoup(int)?throw(runtime_error);
?????//?ok:?recoup?is?as?restrictive?as?pf1
?????void?(*pf1)(int)?throw(runtime_error)?=?recoup;
?????//?ok:?recoup?is?more?restrictive?than?pf2
?????void?(*pf2)(int)?throw(runtime_error,?logic_error)?=?recoup;
?????//?error:?recoup?is?less?restrictive?than?pf3
?????void?(*pf3)(int)?throw()?=?recoup;
?????//?ok:?recoup?is?more?restrictive?than?pf4
?????void?(*pf4)(int)?=?recoup;

?

?

第三個(gè)初始化是錯(cuò)誤的。指針聲明指出,pf3 指向不拋出任何異常的函數(shù),但是,recoup 函數(shù)指出它能拋出 runtime_error 類型的異常,recoup 函數(shù)拋出的異常類型超出了 pf3 所指定的,對(duì) pf3 而言,recoup 函數(shù)不是有效的初始化式,并且會(huì)引發(fā)一個(gè)編譯時(shí)錯(cuò)誤。

?

13. 命名空間/名字空間

命名空間可以是不連續(xù)的。與其他作用域不同,命名空間可以在幾個(gè)部分中定義。命名空間由它的分離定義部分的總和構(gòu)成,命名空間是累積的。一個(gè)命名空間的分離部分可以分散在多個(gè)文件中,在不同文本文件中的命名空間定義也是累積的。當(dāng)然,名字只在聲明名字的文件中可見,這一常規(guī)限制繼續(xù)應(yīng)用,所以,如果命名空間的一個(gè)部分需要定義在另一文件中的名字,仍然必須聲明該名字。

定義多個(gè)不相關(guān)類型的命名空間應(yīng)該使用分離的文件,表示該命名空間定義的每個(gè)類型。

(1)未命名的名字空間

未命名的命名空間與其他命名空間不同,未命名的命名空間的定義局部于特定文件,從不跨越多個(gè)文本文件。

未命名的命名空間可以在給定文件中不連續(xù),但不能跨越文件,每個(gè)文件有自己的未命名的命名空間。

未命名的命名空間中定義的名字可直接使用,畢竟,沒有命名空間名字來限定它們。不能使用作用域操作符來引用未命名的命名空間的成員。

未命名的命名空間中定義的名字只在包含該命名空間的文件中可見。如果另一文件包含一個(gè)未命名的命名空間,兩個(gè)命名空間不相關(guān)。兩個(gè)命名空間可以定義相同的名字,而這些定義將引用不同的實(shí)體。

未命名空間中定義的名字可以在定義該命名空間所在的作用域中找到。如果在文件的最外層作用域中定義未命名的命名空間,那么,未命名的空間中的名字必須與全局作用域中定義的名字不同:

int?i;???//?global?declaration?for?i
?????namespace?{
?????????int?i;
?????}
?????//?error:?ambiguous?defined?globally?and?in?an?unnested,?unnamed?namespace
?????i?=?10;

?

像任意其他命名空間一樣,未命名的命名空間也可以嵌套在另一命名空間內(nèi)部。如果未命名的命名空間是嵌套的,其中的名字按常規(guī)方法使用外圍命名空間名字訪問:

namespace?local?{
????????namespace?{
????????????int?i;
????????}
?????}
????????//?ok:?i?defined?in?a?nested?unnamed?namespace?is?distinct?from?global?i
????????local::i?=?42;

?

在標(biāo)準(zhǔn) C++ 中引入命名空間之前,程序必須將名字聲明為 static,使它們局部于一個(gè)文件。文件中靜態(tài)聲明的使用從 C 語言繼承而來,在 C 語言中,聲明為 static 的局部實(shí)體在聲明它的文件之外不可見。

????C++ 不贊成文件靜態(tài)聲明。不造成的特征是在未來版本中可能不支持的特征。應(yīng)該避免文件靜態(tài)而使用未命名空間代替。

?

14. 多重繼承與虛繼承

在多重繼承下,派生類的對(duì)象包含每個(gè)基類的基類子對(duì)象。

虛繼承來解決菱形繼承中的多個(gè)基類子對(duì)象的問題。

在 C++ 中,通過使用虛繼承解決這類問題。虛繼承是一種機(jī)制,類通過虛繼承指出它希望共享其虛基類的狀態(tài)。在虛繼承下,對(duì)給定虛基類,無論該類在派生層次中作為虛基類出現(xiàn)多少次,只繼承一個(gè)共享的基類子對(duì)象。共享的基類子對(duì)象稱為虛基類。

虛繼承帶來了初始化順序的問題。

通常,每個(gè)類只初始化自己的直接基類。在應(yīng)用于虛基類的進(jìn)修,這個(gè)初始化策略會(huì)失敗。如果使用常規(guī)規(guī)則,就可能會(huì)多次初始化虛基類。類將沿著包含該虛基類的每個(gè)繼承路徑初始化。

為了解決這個(gè)重復(fù)初始化問題,從具有虛基類的類繼承的類對(duì)初始化進(jìn)行特殊處理。在虛派生中,由最低層派生類的構(gòu)造函數(shù)初始化虛基類。

構(gòu)造函數(shù)與析構(gòu)函數(shù)次序:無論虛基類出現(xiàn)在繼承層次中任何地方,總是在構(gòu)造非虛基類之前構(gòu)造虛基類。

代碼如下:

View Code #if?1
#include?<iostream>
class?Class{
????public:
????Class(){
????????std::cout<<"Constructor->Class"<<std::endl;
????}
????~Class(){
????????std::cout<<"Destructor->Class"<<std::endl;
????}

};
class?Base:?public?Class{
????public:
????????Base():name("Base"){std::cout<<"Constructor->Base"<<std::endl;}
????????Base(std::string?s):name(s){std::cout<<"Constructor->Base"<<std::endl;}
????????Base(const?Base&?b):name(b.name){std::cout<<"Constructor->Base"<<std::endl;}
????????~Base(){
????????????std::cout<<"Destructor->Base"<<std::endl;
????????}
????protected:
????????std::string?name;
};
class?Derived1:virtual?public?Base{
????public:
????????Derived1():Base("Derived1"){std::cout<<"Constructor->Derived1"<<std::endl;}
????????Derived1(std::string?s):Base(s){std::cout<<"Constructor->Derived1"<<std::endl;}
????????Derived1(const?Derived1&?d):Base(d){std::cout<<"Constructor->Derived1"<<std::endl;}
????????~Derived1(){
????????????std::cout<<"Destructor->Derived1"<<std::endl;
????????}
};
class?Derived2:virtual?public?Base{
????public:
????????Derived2():Base("Derived2"){std::cout<<"Constructor->Derived2"<<std::endl;}
????????Derived2(std::string?s):Base(s){std::cout<<"Constructor->Derived2"<<std::endl;}
????????Derived2(const?Derived2&?d):Base(d){std::cout<<"Constructor->Derived2"<<std::endl;}
????????~Derived2(){
????????????std::cout<<"Destructor->Derived2"<<std::endl;
????????}
};
class?MI:public?Derived1,public?Derived2{
????public:
????????MI():Base("MI"){}
????????MI(std::string?s):Base(s),Derived1(s),Derived2(s){std::cout<<"Constructor->MI"<<std::endl;}
????????MI(const?MI&?m):Base(m),Derived1(m),Derived2(m){std::cout<<"Constructor->MI"<<std::endl;}
????????~MI(){
????????????std::cout<<"Destructor->MI"<<std::endl;
????????}
};
class?Final:public?MI,public?Class{
????public:
????????Final():Base("Final"){std::cout<<"Constructor->Final"<<std::endl;}
????????Final(std::string?s):Base(s),MI(s){std::cout<<"Constructor->Final"<<std::endl;}
????????Final(const?Final&?f):Base(f),MI(f){std::cout<<"Constructor->Final"<<std::endl;}
????????~Final(){
????????????std::cout<<"Destructor->Final"<<std::endl;
????????}
};

int?main(){
????Final?f;
}

#endif

?

運(yùn)行結(jié)果:

第18章 特殊工具和技術(shù)

1. 優(yōu)化內(nèi)存分配

C++ 的內(nèi)存分配是一種類型化操作:new為特定類型分配內(nèi)存,并在新分配的內(nèi)存中構(gòu)造該類型的一個(gè)對(duì)象。new 表達(dá)式自動(dòng)運(yùn)行合適的構(gòu)造函數(shù)來初始化每個(gè)動(dòng)態(tài)分配的類類型對(duì)象。

new 基于每個(gè)對(duì)象分配內(nèi)存的事實(shí)可能會(huì)對(duì)某些類強(qiáng)加不可接受的運(yùn)行時(shí)開銷,這樣的類可能需要使用用戶級(jí)的類類型對(duì)象分配能夠更快一些。這樣的類使用的通用策略是,預(yù)先分配用于創(chuàng)建新對(duì)象的內(nèi)存,需要時(shí)在預(yù)先分配的內(nèi)存中構(gòu)造每個(gè)新對(duì)象。

另外一些類希望按最小尺寸為自己的數(shù)據(jù)成員分配需要的內(nèi)存。例如,標(biāo)準(zhǔn)庫中的 vector 類預(yù)先分配額外內(nèi)存以保存加入的附加元素,將新元素加入到這個(gè)保留容量中。將元素保持在連續(xù)內(nèi)存中的時(shí)候,預(yù)先分配的元素使 vector 能夠高效地加入元素。

在每種情況下(預(yù)先分配內(nèi)存以保存用戶級(jí)對(duì)象或者保存類的內(nèi)部數(shù)據(jù))都需要將內(nèi)存分配與對(duì)象構(gòu)造分離開。將內(nèi)存分配與對(duì)象構(gòu)造分離開的明顯的理由是,在預(yù)先分配的內(nèi)存中構(gòu)造對(duì)象很浪費(fèi),可能會(huì)創(chuàng)建從不使用的對(duì)象。當(dāng)實(shí)際使用預(yù)先分配的對(duì)象的時(shí)候,被使用的對(duì)象必須重新賦以新值。更微妙的是,如果預(yù)先分配的內(nèi)存必須被構(gòu)造,某些類就不能使用它。例如,考慮 vector,它使用了預(yù)先分配策略。如果必須構(gòu)造預(yù)先分配的內(nèi)存中的對(duì)象,就不能有基類型為沒有默認(rèn)構(gòu)造函數(shù)的 vector——vector 沒有辦法知道怎樣構(gòu)造這些對(duì)象。

2. C++中的內(nèi)存分配

C++ 中,內(nèi)存分配和對(duì)象構(gòu)造緊密糾纏,就像對(duì)象和內(nèi)存回收一樣。使用 new 表達(dá)式的時(shí)候,分配內(nèi)存,并在該內(nèi)存中構(gòu)造一個(gè)對(duì)象;使用 delete 表達(dá)式的時(shí)候,調(diào)用析構(gòu)函數(shù)撤銷對(duì)象,并將對(duì)象所用內(nèi)存返還給系統(tǒng)。

接管內(nèi)存分配時(shí),必須處理這兩個(gè)任務(wù)。分配原始內(nèi)存時(shí),必須在該內(nèi)存中構(gòu)造對(duì)象;在釋放該內(nèi)存之前,必須保證適當(dāng)?shù)爻蜂N這些對(duì)象。

C++ 提供下面兩種方法分配和釋放未構(gòu)造的原始內(nèi)存。

(1).allocator 類,它提供可感知類型的內(nèi)存分配。這個(gè)類支持一個(gè)抽象接口,以分配內(nèi)存并隨后使用該內(nèi)存保存對(duì)象。

(2).標(biāo)準(zhǔn)庫中的 operator new 和 operator delete,它們分配和釋放需要大小的原始的、未類型化的內(nèi)存。

C++ 還提供不同的方法在原始內(nèi)存中構(gòu)造和撤銷對(duì)象。

(1).allocator 類定義了名為 construct 和 destroy 的成員,其操作正如它們的名字所指出的那樣:construct 成員在未構(gòu)造內(nèi)存中初始化對(duì)象,destroy 成員在對(duì)象上運(yùn)行適當(dāng)?shù)奈鰳?gòu)函數(shù)。

(2).定位 new 表達(dá)式(placement new expression)接受指向未構(gòu)造內(nèi)存的指針,并在該空間中初始化一個(gè)對(duì)象或一個(gè)數(shù)組。

(3).可以直接調(diào)用對(duì)象的析構(gòu)函數(shù)來撤銷對(duì)象。運(yùn)行析構(gòu)函數(shù)并不釋放對(duì)象所在的內(nèi)存。

(4).算法 uninitialized_fill 和 uninitialized_copy 像 fill 和 copy 算法一樣執(zhí)行,除了它們的目的地構(gòu)造對(duì)象而不是給對(duì)象賦值之外。

3. allocator類

allocator 類將內(nèi)存分配和對(duì)象構(gòu)造分開。當(dāng) allocator 對(duì)象分配內(nèi)存的時(shí)候,它分配適當(dāng)大小并排列成保存給定類型對(duì)象的空間。但是,它分配的內(nèi)存是未構(gòu)造的,allocator 的用戶必須分別 construct 和 destroy 放置在該內(nèi)存中的對(duì)象。

回憶一下,vector 類將元素保存在連續(xù)的存儲(chǔ)中。為了獲得可接受的性能,vector 預(yù)先分配比所需元素更多的元素。每個(gè)將元素加到容器中的 vector 成員檢查是否有可用空間以容納另一元素。如果有,該成員在預(yù)分配內(nèi)存中下一可用位置初始化一個(gè)對(duì)象;如果沒有自由元素,就重新分配 vector:vector 獲取新的空間,將現(xiàn)在元素復(fù)制到空間,增加新元素,并釋放舊空間。

vector 所用存儲(chǔ)開始是未構(gòu)造內(nèi)存,它還沒有保存任何對(duì)象。將元素復(fù)制或增加到這個(gè)預(yù)分配空間的時(shí)候,必須使用 allocator 類的 construct 成員構(gòu)造元素。

?

為了說明這些概念,我們將實(shí)現(xiàn) vector 的一小部分。將我們的類命名為 Vector,以區(qū)別于標(biāo)準(zhǔn)類 vector:

//?pseudo-implementation?of?memory?allocation?strategy?for?a?vector-like?class
?????template?<class?T>?class?Vector?{
?????public:
?????????Vector():?elements(0),?first_free(0),?end(0)?{?}
?????????void?push_back(const?T&);
??????????//?...
?????private:
?????????static?std::allocator<T>?alloc;?//?object?to?get?raw?memory
?????????void?reallocate();?//?get?more?space?and?copy?existing?elements
?????????T*?elements;???????//?pointer?to?first?element?in?the?array
?????????T*?first_free;?????//?pointer?to?first?free?element?in?the?array
?????????T*?end;????????????//?pointer?to?one?past?the?end?of?the?array
?????????
//?...
?????};

每個(gè) Vector<T> 類型定義一個(gè) allocator<T> 類型的 static 數(shù)據(jù)成員,以便在給定類型的 Vector 中分配和構(gòu)造元素。每個(gè) Vector 對(duì)象在指定類型的內(nèi)置數(shù)組中保存其元素,并維持該數(shù)組的下列三個(gè)指針:

  • elements,指向數(shù)組的第一個(gè)元素。
  • first_free,指向最后一個(gè)實(shí)際元素之后的那個(gè)元素。
  • end,指向數(shù)組本身之后的那個(gè)元素。

可以使用這些指針來確定 Vector 的大小和容量:

  • Vector 的 size(實(shí)際使用的元素的數(shù)目)等于 first_free-elements。
  • Vector 的 capacity(在必須重新分配 Vector 之前,可以定義的元素的總數(shù))等于end-elements。
  • 自由空間(在需要重新分配之前,可以增加的元素的數(shù)目)是 end-first_free。

    ?

push_back 成員使用這些指針將新元素加到 Vector 末尾:

template?<class?T>
?????void?Vector<T>::push_back(const?T&?t)
?????{
?????????//?are?we?out?of?space?
?????????if?(first_free?==?end)
???????????reallocate();?//?gets?more?space?and?copies?existing?elements?to?it
?????????alloc.construct(first_free,?t);
?????????++first_free;
?????}

push_back 函數(shù)首先確定是否有可用空間,如果沒有,就調(diào)用 reallocate 函數(shù),reallocate 分配新空間并復(fù)制現(xiàn)存元素,將指針重置為指向新分配的空間。

一旦 push_back 函數(shù)知道還有空間容納新元素,它就請(qǐng)求 allocator 對(duì)象構(gòu)造一個(gè)新的最后元素。construct 函數(shù)使用類型 T 的復(fù)制構(gòu)造函數(shù)將 t 值復(fù)制到由 first_free 指出的元素,然后,將 first_free 加 1 以指出又有一個(gè)元素在用。

?

reallocate 函數(shù)所做的工作最多:

template?<class?T>?void?Vector<T>::reallocate()
?????{
?????????//?compute?size?of?current?array?and?allocate?space?for?twice?as?many?elements
?????????std::ptrdiff_t?size?=?first_free?-?elements;
?????????std::ptrdiff_t?newcapacity?=?2?*?max(size,?1);
?????????//?allocate?space?to?hold?newcapacity?number?of?elements?of?type?T
?????????T*?newelements?=?alloc.allocate(newcapacity);
?????????//?construct?copies?of?the?existing?elements?in?the?new?space
?????????uninitialized_copy(elements,?first_free,?newelements);
?????????//?destroy?the?old?elements?in?reverse?order
?????????for?(T?*p?=?first_free;?p?!=?elements;?/*?empty?*/?)
????????????alloc.destroy(--p);
?????????//?deallocate?cannot?be?called?on?a?0?pointer
?????????if?(elements)
?????????????//?return?the?memory?that?held?the?elements
?????????????alloc.deallocate(elements,?end?-?elements);
?????????//?make?our?data?structure?point?to?the?new?elements
?????????elements?=?newelements;
?????????first_free?=?elements?+?size;
?????????end?=?elements?+?newcapacity;
?????}

?

我們使用一個(gè)簡(jiǎn)單但效果驚人的策略:每次重新分配時(shí)分配兩倍內(nèi)存。函數(shù)首先計(jì)算當(dāng)前在用的元素?cái)?shù)目,將該數(shù)目翻倍,并請(qǐng)求 allocator 對(duì)象來獲得所需數(shù)量的空間。如果 Vector 為空,就分配兩個(gè)元素。

如果 Vector 保存 int 值,allocate 函數(shù)調(diào)用為 newcapacity 數(shù)目的 int 值分配空間;如果 Vector 保存 string 對(duì)象,它就為給定數(shù)目的 string 對(duì)象分配空間。

uninitialized_copy 調(diào)用使用標(biāo)準(zhǔn) copy 算法的特殊版本。這個(gè)版本希望目的地是原始的未構(gòu)造內(nèi)存,它在目的地復(fù)制構(gòu)造每個(gè)元素,而不是將輸入范圍的元素賦值給目的地,使用 T 的復(fù)制構(gòu)造函數(shù)從輸入范圍將每個(gè)元素復(fù)制到目的地。

for 循環(huán)對(duì)舊數(shù)組中每個(gè)對(duì)象調(diào)用 allocator 的 destroy 成員它按逆序撤銷元素,從數(shù)組中最后一個(gè)元素開始,以第一個(gè)元素結(jié)束。destroy 函數(shù)運(yùn)行 T 類型的析構(gòu)函數(shù)來釋放舊元素所用的任何資源。

一旦復(fù)制和撤銷了元素,就釋放原來數(shù)組所用的空間。在調(diào)用 deallocate 之前,必須檢查 elements 是否實(shí)際指向一個(gè)數(shù)組。

最后,必須重置指針以指向新分配并初始化的數(shù)組。將 first_free 和 end 指針分別置為指向最后構(gòu)造的元素之后的單元以及所分配空間末尾的下一單元。

?

完整的Vector的代碼如下:

View Code #include?<iostream>
#include?<memory>
using?std::cout;?using?std::endl;

template?<class?T>?class?Vector?{
public:
????Vector():?elements(0),?first_free(0),?end(0)?{?}
????void?push_back(const?T&);
????size_t?size()?const?{?return?first_free?-?elements;?}
????size_t?capacity()?const?{?return?end?-?elements;?}
????//?.?.?.
????T&?operator[](size_t?n)?{?return?elements[n];?}
????const?T&?operator[](size_t?n)?const?{?return?elements[n];?}
private:
????static?std::allocator<T>?alloc;?//?member?to?handle?allocation
????void?reallocate();?//?get?more?space?and?copy?existing?elements
????T*?elements;???????//?pointer?to?first?element?in?the?array
????T*?first_free;?????//?pointer?to?first?free?element?in?the?array
????T*?end;????????????//?pointer?to?one?past?the?end?of?the?array
????
//?.?.?.
};

#include?<algorithm>
using?std::allocator;
template?<class?T>?allocator<T>?Vector<T>::alloc;

using?std::max;
using?std::uninitialized_copy;
template?<class?T>?void?Vector<T>::reallocate()
{
????std::ptrdiff_t?size?=?first_free?-?elements;?
????std::ptrdiff_t?newcapacity?=?2?*?max(size,?1);
????T*?newelements?=?alloc.allocate(newcapacity);
?
????uninitialized_copy(elements,?first_free,?newelements);

????for?(T?*p?=?first_free;?p?!=?elements;?/*empty*/?)
????????alloc.destroy(--p);
????
????if?(elements)
????????alloc.deallocate(elements,?end?-?elements);

????elements?=?newelements;
????first_free?=?elements?+?size;
????end?=?elements?+?newcapacity;
}

template?<class?T>?void?Vector<T>::push_back(const?T&?t)
{
????if?(first_free?==?end)
??????reallocate();?//?gets?more?space?and?copies?existing?elements?to?it
????alloc.construct(first_free,?t);??
????++first_free;
}

int?main()
{
????Vector<int>?vi;

????for?(int?i?=?0;?i?!=?10;?++i)?{
??????vi.push_back(i);
??????cout?<<?vi[i]?<<?endl;
????}

????for?(int?i?=?0;?i?!=?10;?++i)
??????cout?<<?vi[i]?<<?endl;

????return?0;
}

?

4. new和delete表達(dá)式的工作原理:

當(dāng)使用 new 表達(dá)式

//?new?expression
string?*?sp?=?new?string("initialized");

的時(shí)候,實(shí)際上發(fā)生三個(gè)步驟。首先,該表達(dá)式調(diào)用名為 operator new 的標(biāo)準(zhǔn)庫函數(shù),分配足夠大的原始的未類型化的內(nèi)存,以保存指定類型的一個(gè)對(duì)象;接下來,運(yùn)行該類型的一個(gè)構(gòu)造函數(shù),用指定初始化式構(gòu)造對(duì)象;最后,返回指向新分配并構(gòu)造的對(duì)象的指針。

當(dāng)使用 delete 表達(dá)式

delete?sp;

?

刪除動(dòng)態(tài)分配對(duì)象的時(shí)候,發(fā)生兩個(gè)步驟。首先,對(duì) sp 指向的對(duì)象運(yùn)行適當(dāng)?shù)奈鰳?gòu)函數(shù);然后,通過調(diào)用名為 operator delete 的標(biāo)準(zhǔn)庫函數(shù)釋放該對(duì)象所用內(nèi)存。

?

5. new表達(dá)式與operator new函數(shù)

標(biāo)準(zhǔn)庫函數(shù) operator new 和 operator delete 的命名容易讓人誤解。與其他 operator 函數(shù)(如 operator=)不同,這些函數(shù)沒有重載 new 或 delete 表達(dá)式,實(shí)際上,我們不能重定義 new 和 delete 表達(dá)式的行為。

通過調(diào)用 operator new 函數(shù)執(zhí)行 new 表達(dá)式獲得內(nèi)存,并接著在該內(nèi)存中構(gòu)造一個(gè)對(duì)象,通過撤銷一個(gè)對(duì)象執(zhí)行 delete 表達(dá)式,并接著調(diào)用 operator delete 函數(shù),以釋放該對(duì)象使用的內(nèi)存。

?

6. operator new 函數(shù)和 operator delete 函數(shù)

(1) operator new 和 operator delete 接口如下:

operator new 和 operator delete 函數(shù)有兩個(gè)重載版本,每個(gè)版本支持相關(guān)的 new 表達(dá)式和 delete 表達(dá)式:

void?*operator?new(size_t);?//?allocate?an?object
void?*operator?new[](size_t);?//?allocate?an?array
void?*operator?delete(void*);?//?free?an?object
void?*operator?delete[](void*);?//?free?an?array

(2)使用分配操作符函數(shù)

雖然 operator new 和 operator delete 函數(shù)的設(shè)計(jì)意圖是供 new 表達(dá)式使用,但它們通常是標(biāo)準(zhǔn)庫中的可用函數(shù)。可以使用它們獲得未構(gòu)造內(nèi)存,它們有點(diǎn)類似 allocate 類的 allocator 和 deallocate 成員。例如,代替使用 allocator 對(duì)象,可以在 Vector 類中使用 operator new 和 operator delete 函數(shù)。在分配新空間時(shí)我們?cè)帉?

//?allocate?space?to?hold?newcapacity?number?of?elements?of?type?T
T*?newelements?=?alloc.allocate(newcapacity);

這可以重新編寫為

//?allocate?unconstructed?memory?to?hold?newcapacity?elements?of?type?T
T*?newelements?=?static_cast<T*>(operator?new[](newcapacity?*?sizeof(T)));

?

類似地,在重新分配由 Vector 成員 elements 指向的舊空間的時(shí)候,我們?cè)?jīng)編寫

//?return?the?memory?that?held?the?elements
alloc.deallocate(elements,?end?-?elements);

?

這可以重新編寫為

//?deallocate?the?memory?that?they?occupied
operator?delete[](elements);

?

這些函數(shù)的表現(xiàn)與 allocate 類的 allocator 和 deallocate 成員類似。但是,它們?cè)谝粋€(gè)重要方面有不同:它們?cè)?void* 指針而不是類型化的指針上進(jìn)行操作。

一般而言,使用 allocator 比直接使用 operator new 和 operator delete 函數(shù)更為類型安全。

allocate 成員分配類型化的內(nèi)存,所以使用它的程序可以不必計(jì)算以字節(jié)為單位的所需內(nèi)存量,它們也可以避免對(duì) operator new 的返回值進(jìn)行強(qiáng)制類型轉(zhuǎn)換。類似地,deallocate 釋放特定類型的內(nèi)存,也不必轉(zhuǎn)換為 void*。

?

7. 定位new表達(dá)式

標(biāo)準(zhǔn)庫函數(shù) operator new 和 operator delete 是 allocator 的 allocate 和 deallocate 成員的低級(jí)版本,它們都分配但不初始化內(nèi)存。

allocator 的成員 construct 和 destroy 也有兩個(gè)低級(jí)選擇,這些成員在由 allocator 對(duì)象分配的空間中初始化和撤銷對(duì)象。

類似于 construct 成員,有第三種 new 表達(dá)式,稱為定位 new。定位 new 表達(dá)式在已分配的原始內(nèi)存中初始化一個(gè)對(duì)象,它與 new 的其他版本的不同之處在于,它不分配內(nèi)存。相反,它接受指向已分配但未構(gòu)造內(nèi)存的指針,并在該內(nèi)存中初始化一個(gè)對(duì)象。實(shí)際上,定位 new 表達(dá)式使我們能夠在特定的、預(yù)分配的內(nèi)存地址構(gòu)造一個(gè)對(duì)象。

?

定位 new 表達(dá)式的形式是:

new (place_address) type

new (place_address) type (initializer-list)

其中 place_address 必須是一個(gè)指針,而 initializer-list 提供了(可能為空的)初始化列表,以便在構(gòu)造新分配的對(duì)象時(shí)使用。

?

可以使用定位 new 表達(dá)式代替 Vector 實(shí)現(xiàn)中的 construct 調(diào)用。原來的代碼

//?construct?a?copy?t?in?the?element?to?which?first_free?points
alloc.construct?(first_free,?t);

?

可以用等價(jià)的定位 new 表達(dá)式代替

//?copy?t?into?element?addressed?by?first_free
new?(first_free)?T(t);

?

定位 new 表達(dá)式比 allocator 類的 construct 成員更靈活。定位 new 表達(dá)式初始化一個(gè)對(duì)象的時(shí)候,它可以使用任何構(gòu)造函數(shù),并直接建立對(duì)象。construct 函數(shù)總是使用復(fù)制構(gòu)造函數(shù)。

例如,可以用下面兩種方式之一,從一對(duì)迭代器初始化一個(gè)已分配但未構(gòu)造的 string 對(duì)象:

allocator<string>?alloc;
string?*sp?=?alloc.allocate(2);?//?allocate?space?to?hold?2?strings
//?two?ways?to?construct?a?string?from?a?pair?of?iterators
new?(sp)?string(b,?e);?//?construct?directly?in?place
alloc.construct(sp?+?1,?string(b,?e));?//?build?and?copy?a?temporary

定位 new 表達(dá)式使用了接受一對(duì)迭代器的 string 構(gòu)造函數(shù),在 sp 指向的空間直接構(gòu)造 string 對(duì)象。當(dāng)調(diào)用 construct 函數(shù)的時(shí)候,必須首先從迭代器構(gòu)造一個(gè) string 對(duì)象,以獲得傳遞給 construct 的 string 對(duì)象,然后,該函數(shù)使用 string 的復(fù)制構(gòu)造函數(shù),將那個(gè)未命名的臨時(shí) string 對(duì)象復(fù)制到 sp 指向的對(duì)象中。

?

通常,這些區(qū)別是不相干的:對(duì)值型類而言,在適當(dāng)?shù)奈恢弥苯訕?gòu)造對(duì)象與構(gòu)造臨時(shí)對(duì)象并進(jìn)行復(fù)制之間沒有可觀察到的區(qū)別,而且性能差別基本沒有意義。但對(duì)某些類而言,使用復(fù)制構(gòu)造函數(shù)是不可能的(因?yàn)閺?fù)制構(gòu)造函數(shù)是私有的),或者是應(yīng)該避免的,在這種情況下,也許有必要使用定位 new 表達(dá)式。

8. 顯示析構(gòu)函數(shù)的調(diào)用

正如定位 new 表達(dá)式是使用 allocate 類的 construct 成員的低級(jí)選擇,我們可以使用析構(gòu)函數(shù)的顯式調(diào)用作為調(diào)用 destroy 函數(shù)的低級(jí)選擇。

在使用 allocator 對(duì)象的 Vector 版本中,通過調(diào)用 destroy 函數(shù)清除每個(gè)元素:

//?destroy?the?old?elements?in?reverse?order
for?(T?*p?=?first_free;?p?!=?elements;?/*?empty?*/?)
alloc.destroy(--p);

?

對(duì)于使用定位 new 表達(dá)式構(gòu)造對(duì)象的程序,顯式調(diào)用析構(gòu)函數(shù):

for?(T?*p?=?first_free;?p?!=?elements;?/*?empty?*/?)
p->~T();?//?call?the?destructor

?

在這里直接調(diào)用析構(gòu)函數(shù)。箭頭操作符對(duì)迭代器 p 解引用以獲得 p 所指的對(duì)象,然后,調(diào)用析構(gòu)函數(shù),析構(gòu)函數(shù)以類名前加 ~ 來命名。

顯式調(diào)用析構(gòu)函數(shù)的效果是適當(dāng)?shù)厍宄龑?duì)象本身。但是,并沒有釋放對(duì)象所占的內(nèi)存,如果需要,可以重用該內(nèi)存空間。

?

9. 運(yùn)行時(shí)類型識(shí)別

通過運(yùn)行時(shí)類型識(shí)別(RTTI),程序能夠使用基類的指針或引用來檢索這些指針或引用所指對(duì)象的實(shí)際派生類型。

通過下面兩個(gè)操作符提供 RTTI:

(1) typeid 操作符,返回指針或引用所指對(duì)象的實(shí)際類型。

(2) dynamic_cast 操作符,將基類類型的指針或引用安全地轉(zhuǎn)換為派生類型的指針或引用。

這些操作符只為帶有一個(gè)或多個(gè)虛函數(shù)的類返回動(dòng)態(tài)類型信息,對(duì)于其他類型,返回靜態(tài)(即編譯時(shí))類型的信息。

對(duì)于帶虛函數(shù)的類,在運(yùn)行時(shí)執(zhí)行 RTTI 操作符,但對(duì)于其他類型,在編譯時(shí)計(jì)算 RTTI 操作符。

當(dāng)具有基類的引用或指針,但需要執(zhí)行不是基類組成部分的派生類操作的時(shí)候,需要?jiǎng)討B(tài)的強(qiáng)制類型轉(zhuǎn)換。通常,從基類指針獲得派生類行為最好的方法是通過虛函數(shù)。當(dāng)使用虛函數(shù)的時(shí)候,編譯器自動(dòng)根據(jù)對(duì)象的實(shí)際類型選擇正確的函數(shù)。

但是,在某些情況下,不可能使用虛函數(shù)。在這些情況下,RTTI 提供了可選的機(jī)制。然而,這種機(jī)制比使用虛函數(shù)更容易出錯(cuò):程序員必須知道應(yīng)該將對(duì)象強(qiáng)制轉(zhuǎn)換為哪種類型,并且必須檢查轉(zhuǎn)換是否成功執(zhí)行了。

????使用動(dòng)態(tài)強(qiáng)制類型轉(zhuǎn)換要小心。只要有可能,定義和使用虛函數(shù)比直接接管類型管理好得多。

?

10. dynamic_cast操作符

可以使用 dynamic_cast 操作符將基類類型對(duì)象的引用或指針轉(zhuǎn)換為同一繼承層次中其他類型的引用或指針。與 dynamic_cast 一起使用的指針必須是有效的——它必須為 0 或者指向一個(gè)對(duì)象。

與其他強(qiáng)制類型轉(zhuǎn)換不同,dynamic_cast 涉及運(yùn)行時(shí)類型檢查。如果綁定到引用或指針的對(duì)象不是目標(biāo)類型的對(duì)象,則 dynamic_cast 失敗。如果轉(zhuǎn)換到指針類型的 dynamic_cast 失敗,則 dynamic_cast 的結(jié)果是 0 值;如果轉(zhuǎn)換到引用類型的 dynamic_cast 失敗,則拋出一個(gè) bad_cast 類型的異常。

因此,dynamic_cast 操作符一次執(zhí)行兩個(gè)操作。它首先驗(yàn)證被請(qǐng)求的轉(zhuǎn)換是否有效,只有轉(zhuǎn)換有效,操作符才實(shí)際進(jìn)行轉(zhuǎn)換。一般而言,引用或指針?biāo)壎ǖ膶?duì)象的類型在編譯時(shí)是未知的,基類的指針可以賦值為指向派生類對(duì)象,同樣,基類的引用也可以用派生類對(duì)象初始化,因此,dynamic_cast 操作符執(zhí)行的驗(yàn)證必須在運(yùn)行時(shí)進(jìn)行。

作為例子,假定 Base 是至少帶一個(gè)虛函數(shù)的類,并且 Derived 類派生于 Base 類。如果有一個(gè)名為 basePtr 的指向 Base 的指針,就可以像這樣在運(yùn)行時(shí)將它強(qiáng)制轉(zhuǎn)換為指向 Derived 的指針:

if?(Derived?*derivedPtr?=?dynamic_cast<Derived*>(basePtr))
{
//?use?the?Derived?object?to?which?derivedPtr?points
}?else?{?//?BasePtr?points?at?a?Base?object
//?use?the?Base?object?to?which?basePtr?points
}

?

在前面例子中,使用了 dynamic_cast 將基類指針轉(zhuǎn)換為派生類指針,也可以使用 dynamic_cast 將基類引用轉(zhuǎn)換為派生類引用,這種 dynamic_cast 操作的形式如下:

dynamic_cast< Type& >(val)

這里,Type 是轉(zhuǎn)換的目標(biāo)類型,而 val 是基類類型的對(duì)象。

只有當(dāng) val 實(shí)際引用一個(gè) Type 類型對(duì)象,或者 val 是一個(gè) Type 派生類型的對(duì)象的時(shí)候,dynamic_cast 操作才將操作數(shù) val 轉(zhuǎn)換為想要的 Type& 類型。

?

因?yàn)椴淮嬖诳找?#xff0c;所以不可能對(duì)引用使用用于指針強(qiáng)制類型轉(zhuǎn)換的檢查策略,相反,當(dāng)轉(zhuǎn)換失敗的時(shí)候,它拋出一個(gè) std::bad_cast 異常,該異常在庫頭文件 typeinfo 中定義。

?

可以重寫前面的例子如下,以便使用引用:

void?f(const?Base?&b)
{
try?{
const?Derived?&d?=?dynamic_cast<const?Derived&>(b);
//?use?the?Derived?object?to?which?b?referred
}?catch?(bad_cast)?{
//?handle?the?fact?that?the?cast?failed
}
}

?

11. 使用dynamic_cast代替虛函數(shù)

12. typeid操作符

如果表達(dá)式的類型是類類型且該類包含一個(gè)或多個(gè)虛函數(shù),則表達(dá)式的動(dòng)態(tài)類型可能不同于它的靜態(tài)編譯時(shí)類型。例如,如果表達(dá)式對(duì)基類指針解引用,則該表達(dá)式的靜態(tài)編譯時(shí)類型是基類類型;但是,如果指針實(shí)際指向派生類對(duì)象,則 typeid 操作符將說表達(dá)式的類型是派生類型。

typeid 操作符可以與任何類型的表達(dá)式一起使用。內(nèi)置類型的表達(dá)式以及常量都可以用作 typeid 操作符的操作數(shù)。如果操作數(shù)不是類類型或者是沒有虛函數(shù)的類,則 typeid 操作符指出操作數(shù)的靜態(tài)類型;如果操作數(shù)是定義了至少一個(gè)虛函數(shù)的類類型,則在運(yùn)行時(shí)計(jì)算類型。

typeid 操作符的結(jié)果是名為 type_info 的標(biāo)準(zhǔn)庫類型的對(duì)象引用,第 18.2.4 節(jié)將更詳細(xì)地討論這個(gè)類型。要使用 type_info 類,必須包含庫頭文件 typeinfo。

typeid 最常見的用途是比較兩個(gè)表達(dá)式的類型,或者將表達(dá)式的類型與特定類型相比較:

Base?*bp;
?????Derived?*dp;
?????//?compare?type?at?run?time?of?two?objects
?????if?(typeid(*bp)?==?typeid(*dp))?{
?????????//?bp?and?dp?point?to?objects?of?the?same?type
?????}
?????//?test?whether?run?time?type?is?a?specific?type
?????if?(typeid(*bp)?==?typeid(Derived))?{
?????????//?bp?actually?points?to?a?Derived
?????}

?

13. type_info類

type_info 類隨編譯器而變。一些編譯器提供附加的成員函數(shù),那些函數(shù)提供關(guān)于程序中所用類型的附加信息。你應(yīng)該查閱編譯器的參考手冊(cè)來理解所提供的確切的 type_info 支持。

#include?<iostream>
#include?<typeinfo>
#include?<string>
using?std::string;
using?std::cout;?using?std::endl;??????????

struct?Base?{
????virtual?~Base()?{?}
};
struct?Derived?:?Base?{?};
int?main()
{
int?iobj;

cout?<<?typeid(iobj).name()?<<?endl
?????<<?typeid(8.16).name()?<<?endl
?????<<?typeid(std::string).name()?<<?endl
?????<<?typeid(Base).name()?<<?endl
?????<<?typeid(Derived).name()?<<?endl;

return?0;
}

?

14. 類成員指針

可以通過使用稱為成員指針的特殊各類的指針做到這一點(diǎn)。成員指針包含類的類型以及成員的類型。這一事實(shí)影響著怎樣定義成員指針,怎樣將成員指針綁定到函數(shù)或數(shù)據(jù)成員,以及怎樣使用它們。

成員指針只應(yīng)用于類的非 static 成員。static 類成員不是任何對(duì)象的組成部分,所以不需要特殊語法來指向 static 成員,static 成員指針是普通指針。

成員函數(shù)的指針必須在三個(gè)方面與它所指函數(shù)的類型相匹配:

(1)函數(shù)形參的類型和數(shù)目,包括成員是否為 const。

(2)返回類型。

(3)所屬類的類型。

通過指定函數(shù)返回類型、形參表和類來定義成員函數(shù)的指針。

普通指針與成員指針

15. 類成員指針的使用

類似于成員訪問操作符 . 和 ->,.* 和 -> 是兩個(gè)新的操作符,它們使我們能夠?qū)⒊蓡T指針綁定到實(shí)際對(duì)象。這兩個(gè)操作符的左操作數(shù)必須是類類型的對(duì)象或類類型的指針,右操作數(shù)是該類型的成員指針。

(1) 成員指針解引用操作符(.*)從對(duì)象或引用獲取成員。

(2) 成員指針箭頭操作符(->*)通過對(duì)象的指針獲取成員。

?

16. 聯(lián)合Union

聯(lián)合是一種特殊的類。一個(gè) union 對(duì)象可以有多個(gè)數(shù)據(jù)成員,但在任何時(shí)刻,只有一個(gè)成員可以有值。當(dāng)將一個(gè)值賦給 union 對(duì)象的一個(gè)成員的時(shí)候,其他所有都變?yōu)槲炊x的。

(1)沒有靜態(tài)數(shù)據(jù)成員、引用成員或類數(shù)據(jù)成員

某些(但不是全部)類特征同樣適用于 union。例如,像任何類一樣,union 可以指定保護(hù)標(biāo)記使成員成為公用的、私有的或受保護(hù)的。默認(rèn)情況下,union 表現(xiàn)得像 struct:除非另外指定,否則 union 的成員都為 public 成員。

union 也可以定義成員函數(shù),包括構(gòu)造函數(shù)和析構(gòu)函數(shù)。但是,union 不能作為基類使用,所以成員函數(shù)不能為虛數(shù)。

union 不能具有靜態(tài)數(shù)據(jù)成員或引用成員,而且,union 不能具有定義了構(gòu)造函數(shù)、析構(gòu)函數(shù)或賦值操作符的類類型的成員:

union?illegal_members?{
?????????Screen?s;??????//?error:?has?constructor
?????????static?int?is;?//?error:?static?member
?????????int?&rfi;??????//?error:?reference?member
?????????Screen?*ps;????//?ok:?ordinary?built-in?pointer?type
?????};

?

這個(gè)限制包括了具有帶構(gòu)造函數(shù)、析構(gòu)函數(shù)或賦值操作符的成員的類。

(2)嵌套聯(lián)合,匿名聯(lián)合

union 最經(jīng)常用作嵌套類型,其中判別式是外圍類的一個(gè)成員:

class?Token?{
?????public:
?????????//?indicates?which?kind?of?value?is?in?val
?????????enum?TokenKind?{INT,?CHAR,?DBL};
?????????TokenKind?tok;
?????????union?{?????????????//?unnamed?union
?????????????char???cval;
?????????????int????ival;
?????????????double?dval;
?????????}?val;??????????????//?member?val?is?a?union?of?the?3?listed?types
?????};

?

這個(gè)類中,用枚舉對(duì)象 tok 指出 val 成員中存儲(chǔ)了哪種值,val 成員是一個(gè)(未命名的)union,它保存 char、int 或 double 值。

經(jīng)常使用 switch 語句(第 6.6 節(jié))測(cè)試判別式,然后根據(jù) union 中當(dāng)前存儲(chǔ)的值進(jìn)行處理:

Token?token;
?????switch?(token.tok)?{
?????case?Token::INT:
?????????token.val.ival?=?42;?break;
?????case?Token::CHAR:
?????????token.val.cval?=?'a';?break;
?????case?Token::DBL:
?????????token.val.dval?=?3.14;?break;
?????}

?

不用于定義對(duì)象的未命名 union 稱為匿名聯(lián)合。匿名 union 的成員的名字出現(xiàn)在外圍作用域中。例如,使用匿名 union 重寫的 Token 類如下:

class?Token?{
?????public:
?????????//?indicates?which?kind?of?token?value?is?in?val
?????????enum?TokenKind?{INT,?CHAR,?DBL};
?????????TokenKind?tok;
?????????union?{?????????????????//?anonymous?union
?????????????char???cval;
?????????????int????ival;
?????????????double?dval;
?????????};
?????};

?

因?yàn)槟涿?union 不提供訪問其成員的途徑,所以將成員作為定義匿名 union 的作用域的一部分直接訪問。重寫前面的 switch 以便使用類的匿名 union 版本,如下:

Token?token;
?????switch?(token.tok)?{
?????case?Token::INT:
?????????token.ival?=?42;?break;
?????case?Token::CHAR:
?????????token.cval?=?'a';?break;
?????case?Token::DBL:
?????????token.dval?=?3.14;?break;
?????}

?

17. 固有的不可移植的特征

(1)位域

可以聲明一種特殊的類數(shù)據(jù)成員,稱為位域,來保存特定的位數(shù)。當(dāng)程序需要將二進(jìn)制數(shù)據(jù)傳遞給另一程序或硬件設(shè)備的時(shí)候,通常使用位域。

位域必須是整型數(shù)據(jù)類型,可以是 signed 或 unsigned。通過在成員名后面接一個(gè)冒號(hào)以及指定位數(shù)的常量表達(dá)式,指出成員是一個(gè)位域:

typedef?unsigned?int?Bit;
?????class?File?{
?????????Bit?mode:?2;
?????????Bit?modified:?1;
?????????Bit?prot_owner:?3;
?????????Bit?prot_group:?3;
?????????Bit?prot_world:?3;
?????????//?...
?????};

?

(2)volatile限定符

直接處理硬件的程序常具有這樣的數(shù)據(jù)成員,它們的值由程序本身直接控制之外的過程所控制。例如,程序可以包含由系統(tǒng)時(shí)鐘更新的變量。當(dāng)可以用編譯器的控制或檢測(cè)之外的方式改變對(duì)象值的時(shí)候,應(yīng)該將對(duì)象聲明為 volatile。關(guān)鍵字 volatile 是給編譯器的指示,指出對(duì)這樣的對(duì)象不應(yīng)該執(zhí)行優(yōu)化。

用與 const 限定符相同的方式使用 volatile 限定符。volatile 限定符是一個(gè)對(duì)類型的附加修飾符:

volatile?int?display_register;
?????volatile?Task?*curr_task;
?????volatile?int?ixa[max_size];
?????volatile?Screen?bitmap_buf;

?

?

第 4.2.5 節(jié)介紹了 const 限定符與指針的相互作用,volatile 限定符與指針之間也存在同樣的相互作用。可以聲明 volatile 指針、指向 volatile 對(duì)象的指針,以及指向 volatile 對(duì)象的 volatile 指針:

volatile?int?v;?????//?v?is?a?volatile?int
?????int?*volatile?vip;??//?vip?is?a?volatile?pointer?to?int
?????volatile?int?*ivp;??//?ivp?is?a?pointer?to?volatile?int
?????
//?vivp?is?a?volatile?pointer?to?volatile?int
?????volatile?int?*volatile?vivp;
?????int?*ip?=?&v;?//?error:?must?use?pointer?to?volatile
?????*ivp?=?&v;????//?ok:?ivp?is?pointer?to?volatile
?????vivp?=?&v;????//?ok:?vivp?is?volatile?pointer?to?volatile

?

像用 const 一樣,只能將 volatile 對(duì)象的地址賦給指向 volatile 的指針,或者將指向 volatile 類型的指針復(fù)制給指向 volatile 的指針。只有當(dāng)引用為 volatile 時(shí),我們才可以使用 volatile 對(duì)象對(duì)引用進(jìn)行初始化。

?

對(duì)待 const 和 volatile 的一個(gè)重要區(qū)別是,不能使用合成的復(fù)制和賦值操作符從 volatile 對(duì)象進(jìn)行初始化或賦值。合成的復(fù)制控制成員接受 const 形參,這些形參是對(duì)類類型的 const 引用,但是,不能將 volatile 對(duì)象傳遞給普通引用或 const 引用。

如果類希望允許復(fù)制 volatile 對(duì)象,或者,類希望允許從 volatile 操作數(shù)或?qū)?volatile 操作數(shù)進(jìn)行賦值,它必須定義自己的復(fù)制構(gòu)造函數(shù)和/或賦值操作符版本:

class?Foo?{
?????public:
?????????Foo(const?volatile?Foo&);????//?copy?from?a?volatile?object
?????????
//?assign?from?a?volatile?object?to?a?non?volatile?objet
?????????Foo&?operator=(volatile?const?Foo&);
?????????//?assign?from?a?volatile?object?to?a?volatile?object
?????????Foo&?operator=(volatile?const?Foo&)?volatile;
?????????//?remainder?of?class?Foo
?????};

?

通過將復(fù)制控制成員的形參定義為 const volatile 引用,我們可以從任何各類的 Foo 對(duì)象進(jìn)行復(fù)制或賦值:普通 Foo 對(duì)象、const Foo 對(duì)象、volatile Foo 對(duì)象或 const volatile Foo 對(duì)象。

????

雖然可以定義復(fù)制控制成員來處理 volatile 對(duì)象,但更深入的問題是復(fù)制 volatile 對(duì)象是否有意義,對(duì)該問題的回答與任意特定程序中使用 volatile 的原因密切相關(guān)。

?

(3) 鏈接指示:extern "c"

鏈接指示與函數(shù)重載之間的相互作用依賴于目標(biāo)語言。如果語言支持重載函數(shù),則為該語言實(shí)現(xiàn)鏈接指示的編譯器很可能也支持 C++ 的這些函數(shù)的重載。

C++ 保證支持的唯一語言是 C。C 語言不支持函數(shù)重載,所以,不應(yīng)該對(duì)下面的情況感到驚訝:在一組重載函數(shù)中只能為一個(gè) C 函數(shù)指定鏈接指示。用帶給定名字的 C 鏈接聲明多于一個(gè)函數(shù)是錯(cuò)誤的:

//?error:?two?extern?"C"?functions?in?set?of?overloaded?functions
extern?"C"?void?print(const?char*);
extern?"C"?void?print(int);

在 C++ 程序中,重載 C 函數(shù)很常見,但是,重載集合中的其他函數(shù)必須都是 C++ 函數(shù):

class?SmallInt?{?/*?...?*/?};
class?BigNum?{?/*?...?*/?};
//?the?C?function?can?be?called?from?C?and?C++?programs
//?the?C++?functions?overload?that?function?and?are?callable?from?C++
extern?"C"?double?calc(double);
extern?SmallInt?calc(const?SmallInt&);
extern?BigNum?calc(const?BigNum&);

?

可以從 C 程序和 C++ 程序調(diào)用 calc 的 C 版本。其余函數(shù)是帶類型形參的 C++ 函數(shù),只能從 C++ 程序調(diào)用。聲明的次序不重要。

編寫函數(shù)所用的語言是函數(shù)類型的一部分。為了聲明用其他程序設(shè)計(jì)語言編寫的函數(shù)的指針,必須使用鏈接指示:

//?pf?points?to?a?C?function?returning?void?taking?an?int
extern?"C"?void?(*pf)(int);

使用 pf 調(diào)用函數(shù)的時(shí)候,假定該調(diào)用是一個(gè) C 函數(shù)調(diào)用而編譯該函數(shù)。

????C 函數(shù)的指針與 C++ 函數(shù)的指針具有不同的類型,不能將 C 函數(shù)的指針初始化或賦值為 C++ 函數(shù)的指針(反之亦然)。

存在這種不匹配的時(shí)候,會(huì)給出編譯時(shí)錯(cuò)誤:

void?(*pf1)(int);?//?points?to?a?C++?function
extern?"C"?void?(*pf2)(int);?//?points?to?a?C?function
pf1?=?pf2;?//?error:?pf1?and?pf2?have?different?types

?

一些 C++ 編譯器可以接受前面的賦值作為語言擴(kuò)展,盡管嚴(yán)格說來它是非法的。

?

?

轉(zhuǎn)載于:https://www.cnblogs.com/xkfz007/archive/2012/08/15/2639509.html

總結(jié)

以上是生活随笔為你收集整理的读书笔记之:C++ Primer (第4版)及习题(ch12-ch18) [++++]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。