读书笔记之:C++ Primer (第4版)及习题(ch12-ch18) [++++]
第12章 類
1. 類的聲明與定義:前向聲明,不完全類型
2. 從const函數返回*this
3. 可變數據成員mutable
4. 用于const對象的構造函數:構造函數不能聲明為const
5. 構造函數初始化式
構造函數的執行分為兩個階段:初始化階段和普通的計算階段
6. 構造函數初始化列表
7. 默認實參與構造函數
8. 類通常定義一個默認構造函數,不然的話使用起來會很麻煩。
9. 使用默認構造函數
10. 隱式類類型轉換:使用explicit來杜絕隱式類類型的轉換
11. 類成員的顯式初始化,這種顯式初始化的方式是從C繼承來的
12. static類成員
13. static成員函數
14. static成員變量
?
第13章 復制控制
1.C++中的復制控制
2. 復制構造函數
3. 合成復制構造函數
?
4. 禁止復制
?
5. 智能指針,引用計數
智能指針的實現:
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章 重載操作符與轉換
1.可重載的操作符與不可重載的操作符
不要重載具有內置含義的操作符
定義為成員函數或非成員函數
2. 輸出操作符重載
3. 函數對象的函數適配器:綁定器與求反器
?
4. 轉換操作符重載
?
5. 習題
?
第15章 面向對象編程
1. 動態綁定virtual,從派生類到基類的轉換
2. C++中的多態性
3. 虛函數與默認實參
4. 轉換與繼承
5. 派生類到基類的轉換
5.1 引用轉換不同于對象轉換
5.2 派生類對象對基類對象的初始化或賦值:切割
6. 從基類到派生類的轉換
7. 復制控制和繼承
8. 虛析構函數
9. 構造函數和賦值操作符不是虛函數:構造函數不能定義為虛函數,而賦值操作符定義為虛函數的話會令人混淆。
10. 構造函數和析構函數中的虛函數
11. 名字查找與繼承
12. 容器與繼承
容器中如果定義保存基類,那么派生類對象會被切割,如果定義為保持派生類,那么會產生很大問題。
13. 句柄類與繼承
指針型句柄
一個例子如下:
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. 文本查詢
在原來的基礎上添加了~,& 和| 來進行組合查詢。
代碼如下:
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;
}
?
簡單的運行結果如下:
第16章 模板與泛型編程
1. typename與class
只能使用typename來聲明在類內部定義的類型成員。
2. 非類型模板參數
模板形參不一定都是類型。
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);
}
可以使用非類型模板來確定數組的長度。
template?<class?T,size_t?N>size_t?size(T?(&parm)[N]){?????
????return?N;
}
?
3. 模板實例化
在模板實參推斷期間確定模板實參的類型和值。
?
4. 模板推斷過程中涉及的函數實參允許的類型轉換
?
5.應用于非模板形參的常規轉換
6. 模板實參推斷與函數指針
可以使用函數模板對函數指針進行初始化或賦值,這樣做的時候,編譯器使用指針的類型實例化具有適當模板實參的模板版本。
例如,假定有一個函數指針指向返回 int 值的函數,該函數接受兩個形參,都是 const int 引用,可以用該指針指向 compare 的實例化
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 的類型是一個指針,指向"接受兩個 const int& 類型形參并返回 int 值的函數",形參的類型決定了 T 的模板實參的類型,T 的模板實參為 int 型,指針 pf1 引用的是將 T 綁定到 int 的實例化。
????獲取函數模板實例化的地址的時候,上下文必須是這樣的:它允許為每個模板形參確定唯一的類型或值。
如果不能從函數指針類型確定模板實參,就會出錯。例如,假定有兩個名為 func 的函數,每個函數接受一個指向函數實參的指針。func 的第一個版本接受有兩個 const string 引用形參并返回 string 對象的函數的指針,func 的第二個版本接受帶兩個 const int 引用形參并返回 int 值的函數的指針,不能使用 compare 作為傳給 func 的實參:
//?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 的形參類型不可能確定模板實參的唯一類型,對 func 的調用可以實例化下列函數中的任意一個:
compare(const?string&,?const?string&)?????compare(const?int&,?const?int&)
?
?
因為不能為傳給 func 的實參確定唯一的實例化,該調用會產生一個編譯時(或鏈接時)錯誤。
7. 函數模板的顯式實參
(1) 指定顯式模板實參
(2)在返回類型中使用類型形參
(3)顯式實參與函數模板的指針
?
8. 非類型形參的模板實參
9. 類模板中的友元聲明
在類模板中可以出現三種友元聲明,每一種都聲明了與一個或多個實體友元關系:
(1) 普通非模板類或函數的友元聲明,將友元關系授予明確指定的類或函數。
(2) 類模板或函數模板的友元聲明,授予對友元所有實例的訪問權。
(3) 類模板或函數模板的特定實例的訪問權的友元聲明。
10. 成員模板
任意類(模板或非模板)可以擁有本身為類模板或函數模板的成員,這種成員稱為成員模板,成員模板不能為虛。
11. 類模板的static成員
?
12. 模板特化
函數模板特化
?
13. 類模板特化
類模板的部分特化
如果類模板有一個以上的模板形參,我們也許想要特化某些模板形參而非全部。使用類模板的部分特化可以做到這一點:
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>?{
?????????//?...
?????};
?
類模板的部分特化本身也是模板。部分特化的定義看來像模板定義,這種定義以關鍵字 template 開頭,接著是由尖括號(<>)括住的模板形參表。部分特化的模板形參表是對應的類模板定義形參表的子集。some_template 的部分特化只有一個名為 T1 的模板類型形參,第二個模板形參 T2 的實參已知為 int。部分特化的模板形參表只列出未知模板實參的那些形參。
部分特化的定義與通用模板的定義完全不會沖突。部分特化可以具有與通用類模板完全不同的成員集合。類模板成員的通用定義永遠不會用來實例化類模板部分特化的成員。
?
14. 重載與函數模板
確定重載函數模板的調用
可以在不同類型上調用這些函數:
//?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);
?
下面依次介紹每個調用。
compare(1, 0):兩個形參都是 int 類型。候選函數是第一個模板將 T 綁定到 int 的實例化,以及名為 compare 的普通函數。但該普通函數不可行——不能將 int 對象傳給期待 char* 對象的形參。用 int 實例化的函數與該調用完全匹配,所以選擇它。
compare(ivec1.begin(), ivec1.end(), ivec2.begin())
compare(ia1, ia1 + 10, ivec1.begin()):
這兩個調用中,唯一可行的函數是有三個形參的模板的實例化。帶兩個參數的模板和普通非模板函數都不能匹配這兩個調用。
compare(const_arr1, const_arr2): 這個調用正如我們所期待的,調用普通函數。該函數和將 T 綁定到 const char* 的第一個模板都是可行的,也都完全匹配。根據規則 3b,會選擇普通函數。從候選集合中去掉模板實例,只剩下普通函數可行。
compare(ch_arr1, ch_arr2):這個調用也綁定到普通函數。候選者是將 T 綁定到 char* 的函數模板的版本,以及接受 const char* 實參的普通函數,兩個函數都需要稍加轉換將數組 ch_arr1 和 ch_arr2 轉換為指針。因為兩個函數一樣匹配,所以普通函數優先于模板版本。
?
第17章 用于大型程序的工具
1. 拋出類類型的異常
異常是通過拋出對象而引發的。該對象的類型決定應該激活哪個處理代碼。被選中的處理代碼是調用鏈中與該對象類型匹配且離拋出異常位置最近的那個。
異常以類似于將實參傳遞給函數的方式拋出和捕獲。異常可以是可傳給非引用形參的任意類型的對象,這意味著必須能夠復制該類型的對象。
回憶一下,傳遞數組或函數類型實參的時候,該實參自動轉換為一個指針。被拋出的對象將發生同樣的自動轉換,因此,不存在數組或函數類型的異常。相反。相反,如果拋出一個數組,被拋出的對象轉換為指向數組首元素的指針,類似地,如果拋出一個函數,函數被轉換為指向該函數的指針第 7.9 節。
執行 throw 的時候,不會執行跟在 throw 后面的語句,而是將控制從 throw 轉移到匹配的 catch,該 catch 可以是同一函數中局部的 catch,也可以在直接或間接調用發生異常的函數的另一個函數中。控制從一個地方傳到另一地方,這有兩個重要含義:
1. 沿著調用鏈的函數提早退出。第 17.1.2 節將討論函數因異常而退出時會發生什么。
2. 一般而言,在處理異常的時候,拋出異常的塊中的局部存儲不存在了。
因為在處理異常的時候會釋放局部存儲,所以被拋出的對象就不能再局部存儲,而是用 throw 表達式初始化一個稱為異常對象的特殊對象。異常對象由編譯器管理,而且保證駐留在可能被激活的任意 catch 都可以訪問的空間。這個對象由 throw 創建,并被初始化為被拋出的表達式的副本。異常對象將傳給對應的 catch,并且在完全處理了異常之后撤銷。
異常對象通過復制被拋出表達式的結果創建,該結果必須是可以復制的類型
?
2. 異常對象與繼承
當拋出一個表達式的時候,被拋出對象的靜態編譯時類型將決定異常對象的類型。
通常,使用靜態類型拋出對象不成問題。當拋出一個異常的時候,通常在拋出點構造將拋出的對象,該對象表示出了什么問題,所以我們知道確切的異常類型。
3. 異常與指針
用拋出表達式拋出靜態類型時,比較麻煩的一種情況是,在拋出中對指針解引用。對指針解引用的結果是一個對象,其類型與指針的類型匹配。如果指針指向繼承層次中的一種類型,指針所指對象的類型就有可能與指針的類型不同。無論對象的實際類型是什么,異常對象的類型都與指針的靜態類型相匹配。如果該指針是一個指向派生類對象的基類類型指針,則那個對象將被分割,只拋出基類部分。
?
如果拋出指針本身,可能會引發比分割對象更嚴重的問題。具體而言,拋出指向局部對象的指針總是錯誤的,其理由與從函數返回指向局部對象的指針是錯誤的一樣。拋出指針的時候,必須確定進入處理代碼時指針所指向的對象存在。
?
如果拋出指向局部對象的指針,而且處理代碼在另一函數中,則執行處理代碼時指針所指向的對象將不再存在。即使處理代碼在同一函數中,也必須確信指針所指向的對象在 catch 處存在。如果指針指向某個在 catch 之前退出的塊中的對象,那么,將在 catch 之前撤銷該局部對象。
?
4. 棧展開Stack Unwinding
拋出異常的時候,將暫停當前函數的執行,開始查找匹配的 catch 子句。首先檢查 throw 本身是否在 try 塊內部,如果是,檢查與該 catch 相關的 catch 子句,看是否其中之一與拋出對象相匹配。如果找到匹配的 catch,就處理異常;如果找不到,就退出當前函數(釋放當前函數的內在并撤銷局部對象),并且繼續在調用函數中查找。
如果對拋出異常的函數的調用是在 try 塊中,則檢查與該 try 相關的 catch 子句。如果找到匹配的 catch,就處理異常;如果找不到匹配的 catch,調用函數也退出,并且繼續在調用這個函數的函數中查找。
這個過程,稱之為棧展開(stack unwinding),沿嵌套函數調用鏈繼續向上,直到為異常找到一個 catch 子句。只要找到能夠處理異常的 catch 子句,就進入該 catch 子句,并在該處理代碼中繼續執行。當 catch 結束的時候,在緊接在與該 try 塊相關的最后一個 catch 子句之后的點繼續執行。
(1)為局部對象調用析構函數
棧展開期間,提早退出包含 throw 的函數和調用鏈中可能的其他函數。一般而言,這些函數已經創建了可以在退出函數時撤銷的局部對象。因異常而退出函數時,編譯器保證適當地撤銷局部對象。每個函數退出的時候,它的局部存儲都被釋放,在釋放內存之前,撤銷在異常發生之前創建的所有對象。如果局部對象是類類型的,就自動調用該對象的析構函數。通常,編譯器不撤銷內置類型的對象。
棧展開期間,釋放局部對象所用的內存并運行類類型局部對象的析構函數。
如果一個塊直接分配資源,而且在釋放資源之前發生異常,在棧展開期間將不會釋放該資源。例如,一個塊可以通過調用 new 動態分配內存,如果該塊因異常而退出,編譯器不會刪除該指針,已分配的內在將不會釋放。
由類類型對象分配的資源一般會被適當地釋放。運行局部對象的析構函數,由類類型對象分配的資源通常由它們的析構函數釋放。第 17.1.8 節說明面對異常使用類管理資源分配的編程技術
(2)析構函數應該從不拋出異常
棧展開期間會經常執行析構函數。在執行析構函數的時候,已經引發了異常但還沒有處理它。如果在這個過程中析構函數本身拋出新的異常,又會發生什么呢?新的異常應該取代仍未處理的早先的異常嗎?應該忽略析構函數中的異常嗎?
答案是:在為某個異常進行棧展開的時候,析構函數如果又拋出自己的未經處理的另一個異常,將會導致調用標準庫 terminate 函數。一般而言,terminate 函數將調用 abort 函數,強制從整個程序非正常退出。
因為 terminate 函數結束程序,所以析構函數做任何可能導致異常的事情通常都是非常糟糕的主意。在實踐中,因為析構函數釋放資源,所以它不太可能拋出異常。標準庫類型都保證它們的析構函數不會引發異常。
(3)異常與構造函數
與析構函數不同,構造函數內部所做的事情經常會拋出異常。如果在構造函數對象的時候發生異常,則該對象可能只是部分被構造,它的一些成員可能已經初始化,而另一些成員在異常發生之前還沒有初始化。即使對象只是部分被構造了,也要保證將會適當地撤銷已構造的成員。
類似地,在初始化數組或其他容器類型的元素的時候,也可能發生異常,同樣,也要保證將會適當地撤銷已構造的元素。
(4)未捕獲的異常終止程序
不能不處理異常。異常是足夠重要的、使程序不能繼續正常執行的事件。如果找不到匹配的 catch,程序就調用庫函數 terminate。
?
5. 捕獲異常
catch 子句中的異常說明符看起來像只包含一個形參的形參表,異常說明符是在其后跟一個(可選)形參名的類型名。
說明符的類型決定了處理代碼能夠捕獲的異常種類。類型必須是完全類型,即必須是內置類型或者是已經定義的程序員自定義類型。類型的前向聲明不行。
?
當 catch 為了處理異常只需要了解異常的類型的時候,異常說明符可以省略形參名;如果處理代碼需要已發生異常的類型之外的信息,則異常說明符就包含形參名,catch 使用這個名字訪問異常對象。
(1)查找匹配的處理代碼
在查找匹配的 catch 期間,找到的 catch 不必是與異常最匹配的那個 catch,相反,將選中第一個找到的可以處理該異常的 catch。因此,在 catch 子句列表中,最特殊的 catch 必須最先出現。
異常與 catch 異常說明符匹配的規則比匹配實參和形參類型的規則更嚴格,大多數轉換都不允許——除下面幾種可能的區別之外,異常的類型與 catch 說明符的類型必須完全匹配:
*允許從非 const 到 const 的轉換。也就是說,非 const 對象的 throw 可以與指定接受 const 引用的 catch 匹配。
*允許從派生類型型到基類類型的轉換。
*將數組轉換為指向數組類型的指針,將函數轉換為指向函數類型的適當指針。
在查找匹配 catch 的時候,不允許其他轉換。具體而言,既不允許標準算術轉換,也不允許為類類型定義的轉換。
(2)異常說明符
進入 catch 的時候,用異常對象初始化 catch 的形參。像函數形參一樣,異常說明符類型可以是引用。異常對象本身是被拋出對象的副本。是否再次將異常對象復制到 catch 位置取決于異常說明符類型。
如果說明符不是引用,就將異常對象復制到 catch 形參中,catch 操作異常對象的副本,對形參所做的任何改變都只作用于副本,不會作用于異常對象本身。如果說明符是引用,則像引用形參一樣,不存在單獨的 catch 對象,catch 形參只是異常對象的另一名字。對 catch 形參所做的改變作用于異常對象。
(3)異常說明符與繼承
像形參聲明一樣,基類的異常說明符可以用于捕獲派生類型的異常對象,而且,異常說明符的靜態類型決定 catch 子句可以執行的動作。如果被拋出的異常對象是派生類類型的,但由接受基類類型的 catch 處理,那么,catch 不能使用派生類特有的任何成員。
通常,如果 catch 子句處理因繼承而相關的類型的異常,它就應該將自己的形參定義為引用。
如果 catch 形參是引用類型,catch 對象就直接訪問異常對象,catch 對象的靜態類型可以與 catch 對象所引用的異常對象的動態類型不同。如果異常說明符不是引用,則 catch 對象是異常對象的副本,如果 catch 對象是基類類型對象而異常對象是派生類型的,就將異常對象分割(第 15.3.1 節)為它的基類子對象。
而且,正如第 15.2.4 節所介紹的,對象(相對于引用)不是多態的。當通過對象而不是引用使用虛函數的時候,對象的靜態類型和動態類型相同,函數是虛函數也一樣。只有通過引用或指針調用時才發生動態綁定,通過對象調用不進行動態綁定。
(4)catch子句的次序必須反映類型層次
將異常類型組織成類層次的時候,用戶可以選擇應用程序處理異常的粒度級別。例如,只希望清除并退出的應用程序可以定義一個 try 塊,該 try 塊包圍 main 函數中帶有如下 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
????}
有更嚴格實時需求的程序可能需要更好的異常控制,這樣的應用程序將清除導致異常的一切并繼續執行。
因為 catch 子句按出現次序匹配,所以使用來自繼承層次的異常的程序必須將它們的 catch 子句排序,以便派生類型的處理代碼出現在其基類類型的 catch 之前。
?
6. 重新拋出
一般而言,catch 可以改變它的形參。在改變它的形參之后,如果 catch 重新拋出異常,那么,只有當異常說明符是引用的時候,才會傳播那些改變。
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. 捕獲所有異常的代碼
即使函數不能處理被拋出的異常,它也可能想要在隨拋出異常退出之前執行一些動作。除了為每個可能的異常提供特定 catch 子句之外,因為不可能知道可能被拋出的所有異常,所以可以使用捕獲所有異常 catch 子句的。捕獲所有異常的 catch 子句形式為 (...)。例如:
//?matches?any?exception?that?might?be?thrown?????catch?(...)?{
?????????//?place?our?code?here
?????}
?
8. 標準異常類
9. 自動資源釋放
用類管理資源分配。
對析構函數的運行導致一個重要的編程技術的出現,它使程序更為異常安全的。異常安全的意味著,即使發生異常,程序也能正確操作。在這種情況下,"安全"來自于保證"如果發生異常,被分配的任何資源都適當地釋放"。
通過定義一個類來封閉資源的分配和釋放,可以保證正確釋放資源。這一技術常稱為"資源分配即初始化",簡稱 RAII。
應該設計資源管理類,以便構造函數分配資源而析構函數釋放資源。想要分配資源的時候,就定義該類類型的對象。如果不發生異常,就在獲得資源的對象超出作用域的進修釋放資源。更為重要的是,如果在創建了對象之后但在它超出作用域之前發生異常,那么,編譯器保證撤銷該對象,作為展開定義對象的作用域的一部分。
?
10. auto_ptr類
位于頭文件memory中,智能指針
auto_ptr 只能用于管理從 new 返回的一個對象,它不能管理動態分配的數組。
正如我們所見,當 auto_ptr 被復制或賦值的時候,有不尋常的行為,因此,不能將 auto_ptrs 存儲在標準庫容器類型中。
auto_ptr 對象只能保存一個指向對象的指針,并且不能用于指向動態分配的數組,使用 auto_ptr 對象指向動態分配的數組會導致未定義的運行時行為。
每個 auto_ptr 對象綁定到一個對象或者指向一個對象。當 auto_ptr 對象指向一個對象的時候,可以說它"擁有"該對象。當 auto_ptr 對象超出作用域或者另外撤銷的時候,就自動回收 auto_ptr 所指向的動態分配對象。
(1)為異常安全的內存分配使用 auto_ptr
如果通過常規指針分配內在,而且在執行 delete 之前發生異常,就不會自動釋放該內存:
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 之間發生異常,并且該異常不被局部捕獲,就不會執行 delete,則永不回收該內存。
如果使用一個 auto_ptr 對象來代替,將會自動釋放內存,即使提早退出這個塊也是這樣:
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
?
在這個例子中,編譯器保證在展開棧越過 f 之前運行 ap 的析構函數。
?
(2)auto_ptr 是可以保存任何類型指針的模板
auto_ptr 類是接受單個類型形參的模板,該類型指定 auto_ptr 可以綁定的對象的類型,因此,可以創建任何類型的 auto_ptrs:
auto_ptr<string>?ap1(new?string("Brontosaurus"));?
(3)將 auto_ptr 綁定到指針
在最常見的情況下,將 auto_ptr 對象初始化為由 new 表達式返回的對象的地址:
auto_ptr<int>?pi(new?int(1024));這個語句將 pi 初始化為由 new 表達式創建的對象的地址,這個 new 表達式將對象初始化為 1024。
接受指針的構造函數為 explicit(第 12.4.4 節)構造函數,所以必須使用初始化的直接形式來創建 auto_ptr 對象:
//?error:?constructor?that?takes?a?pointer?is?explicit?and?can't?be?used?implicitlyauto_ptr<int>?pi?=?new?int(1024);
auto_ptr<int>?pi(new?int(1024));?//?ok:?uses?direct?initialization
pi 所指的由 new 表達式創建的對象在超出作用域時自動刪除。如果 pi 是局部對象,pi 所指對象在定義 pi 的塊的末尾刪除;如果發生異常,則 pi 也超出作用域,析構函數將自動運行 pi 的析構函數作為異常處理的一部分;如果 pi 是全局對象,就在程序末尾刪除 pi 引用的對象。
(4)使用 auto_ptr 對象
auto_ptr 類定義了解引用操作符(*)和箭頭操作符(->)的重載版本(第 14.6 節),因為 auto_ptr 定義了這些操作符,所以可以用類似于使用內置指針的方式使用 auto_ptr 對象:
?
//?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 的主要目的,在保證自動刪除 auto_ptr 對象引用的對象的同時,支持普通指針式行為。正如我們所見,自動刪除該對象這一事實導致在怎樣復制和訪問它們的地址值方面,auto_ptrs 與普通指針明顯不同。
(5)auto_ptr 對象的復制和賦值是破壞性操作
auto_ptr 和內置指針對待復制和賦值有非常關鍵的重要區別。當復制 auto_ptr 對象或者將它的值賦給其他 auto_ptr 對象的時候,將基礎對象的所有權從原來的 auto_ptr 對象轉給副本,原來的 auto_ptr 對象重置為未綁定狀態。
?
(6)賦值刪除左操作數指向的對象
除了將所有權從右操作數轉給左操作數之外,賦值還刪除左操作數原來指向的對象——假如兩個對象不同。通常自身賦值沒有效果。
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
因為復制和賦值是破壞性操作,所以auto_ptrs不能將 auto_ptr 對象存儲在標準容器中。標準庫的容器類要求在復制或賦值之后兩個對象相等,auto_ptr 不滿足這一要求,如果將 ap2 賦給 ap1,則在賦值之后 ap1 != ap2,復制也類似。
(7)auto_ptr 的默認構造函數
如果不給定初始式,auto_ptr 對象是未綁定的,它不指向對象:
auto_ptr<int>?p_auto;?//?p_autodoesn't?refer?to?any?object?
默認情況下,auto_ptr 的內部指針值置為 0。對未綁定的 auto_ptr 對象解引用,其效果與對未綁定的指針解引用相同——程序出錯并且沒有定義會發生什么:
*p_auto?=?1024;?//?error:?dereference?auto_ptr?that?doesn't?point?to?an?object?
(8)測試 auto_ptr 對象
auto_ptr 類型沒有定義到可用作條件的類型的轉換,相反,要測試 auto_ptr 對象,必須使用它的 get 成員,該成員返回包含在 auto_ptr 對象中的基礎指針:
//?revised?test?to?guarantee?p_auto?refers?to?an?objectif?(p_auto.get())
*p_auto?=?1024;
使用 get 成員初始化其他 auto_ptr 對象違反 auto_ptr 類設計原則:在任意時刻只有一個 auto_ptrs 對象保存給定指針,如果兩個 auto_ptrs 對象保存相同的指針,該指針就會被 delete 兩次。
(9)reset 操作
auto_ptr 對象與內置指針的另一個區別是,不能直接將一個地址(或者其他指針)賦給 auto_ptr 對象:
p_auto?=?new?int(1024);?//?error:?cannot?assign?a?pointer?to?an?auto_ptr?
相反,必須調用 reset 函數來改變指針:
//?revised?test?to?guarantee?p_auto?refers?to?an?objectif?(p_auto.get())
*p_auto?=?1024;
else
//?reset?p_auto?to?a?new?object
p_auto.reset(new?int(1024));
要復位 auto_ptr 對象,可以將 0 傳給 reset 函數。
?
11. auto_ptr的缺陷
auto_ptr 類模板為處理動態分配的內存提供了安全性和便利性的尺度。要正確地使用 auto_ptr 類,必須堅持該類強加的下列限制:
1.不要使用 auto_ptr 對象保存指向靜態分配對象的指針,否則,當 auto_ptr 對象本身被撤銷的時候,它將試圖刪除指向非動態分配對象的指針,導致未定義的行為。
2.永遠不要使用兩個 auto_ptr 對象指向同一對象,導致這個錯誤的一種明顯方式是,使用同一指針來初始化或者 reset 兩個不同的 auto_ptr 對象。另一種導致這個錯誤的微妙方式可能是,使用一個 auto_ptr 對象的 get 函數的結果來初始化或者 reset 另一個 auto_ptr 對象。
3.不要使用 auto_ptr 對象保存指向動態分配數組的指針。當 auto_ptr 對象被刪除的時候,它只釋放一個對象——它使用普通 delete 操作符,而不用數組的 delete [] 操作符。
4.不要將 auto_ptr 對象存儲在容器中。容器要求所保存的類型定義復制和賦值操作符,使它們表現得類似于內置類型的操作符:在復制(或者賦值)之后,兩個對象必須具有相同值,auto_ptr 類不滿足這個要求。
?
12. 異常說明
異常說明跟在函數形參表之后。一個異常說明在關鍵字 throw 之后跟著一個(可能為空的)由圓括號括住的異常類型列表。
空說明列表指出函數不拋出任何異常:
void no_problem() throw();
異常說明是函數接口的一部分,函數定義以及該函數的任意聲明必須具有相同的異常說明。
如果一個函數聲明沒有指定異常說明,則該函數可以拋出任意類型的異常。
(1)違反異常說明
如果函數拋出了沒有在其異常說明中列出的異常,就調用標準庫函數 unexpected。默認情況下,unexpected 函數調用 terminate 函數,terminate 函數一般會終止程序。
(2)確定函數不拋出異常
異常說服有用的一種重要情況是,如果函數可以保證不會拋出任何異常。
確定函數將不拋出任何異常,對函數的用戶和編譯器都有所幫助:知道函數不拋出異常會簡化編寫調用該函數的異常安全的代碼的工作,我們可以知道在調用函數時不必擔心異常,而且,如果編譯器知道不會拋出異常,它就可以執行被可能拋出異常的代碼所抑制的優化。
(3)異常說明與成員函數
像非成員函數一樣,成員函數聲明的異常說明跟在函數形參表之后。例如,C++ 標準庫中的 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 成員函數聲明中,異常說明跟在 const 限定符之后。
(4)異常說明與虛函數
基類中虛函數的異常說明,可以與派生類中對應虛函數的異常說明不同。但是,派生類虛函數的異常說明必須與對應基類虛函數的異常說明同樣嚴格,或者比后者更受限。
這個限制保證,當使用指向基類類型的指針調用派生類虛函數的時候,派生類的異常說明不會增加新的可拋出異常。例如:
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 的聲明是錯誤的,因為它的異常說明在基類 f1 版本列出的異常中增加了一個異常。派生類不能在異常說明列表中增加異常,原因在于,繼承層次的用戶應該能夠編寫依賴于該說明列表的代碼。如果通過基類指針或引用進行函數調用,那么,這些類的用戶所涉及的應該只是在基類中指定的異常。
通過派生類拋出的異常限制為由基類所列出的那些,在編寫代碼時就可以知道必須處理哪些異常。代碼可以依賴于這樣一個事實:基類中的異常列表是虛函數的派生類版本可以拋出的異常列表的超集。例如,當調用 f3 的時候,我們知道只需要處理 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)函數指針異常說明
異常說明是函數類型的一部分。這樣,也可以在函數指針的定義中提供異常說明:
void (*pf)(int) throw(runtime_error);
這個聲明是說,pf 指向接受 int 值的函數,該函數返回 void 對象,該函數只能拋出 runtime_error 類型的異常。如果不提供異常說明,該指針就可以指向能夠拋出任意類型異常的具有匹配類型的函數。
在用另一指針初始化帶異常說明的函數的指針,或者將后者賦值給函數地址的時候,兩個指針的異常說明不必相同,但是,源指針的異常說明必須至少與目標指針的一樣嚴格。
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;
?
?
第三個初始化是錯誤的。指針聲明指出,pf3 指向不拋出任何異常的函數,但是,recoup 函數指出它能拋出 runtime_error 類型的異常,recoup 函數拋出的異常類型超出了 pf3 所指定的,對 pf3 而言,recoup 函數不是有效的初始化式,并且會引發一個編譯時錯誤。
?
13. 命名空間/名字空間
命名空間可以是不連續的。與其他作用域不同,命名空間可以在幾個部分中定義。命名空間由它的分離定義部分的總和構成,命名空間是累積的。一個命名空間的分離部分可以分散在多個文件中,在不同文本文件中的命名空間定義也是累積的。當然,名字只在聲明名字的文件中可見,這一常規限制繼續應用,所以,如果命名空間的一個部分需要定義在另一文件中的名字,仍然必須聲明該名字。
定義多個不相關類型的命名空間應該使用分離的文件,表示該命名空間定義的每個類型。
(1)未命名的名字空間
未命名的命名空間與其他命名空間不同,未命名的命名空間的定義局部于特定文件,從不跨越多個文本文件。
未命名的命名空間可以在給定文件中不連續,但不能跨越文件,每個文件有自己的未命名的命名空間。
未命名的命名空間中定義的名字可直接使用,畢竟,沒有命名空間名字來限定它們。不能使用作用域操作符來引用未命名的命名空間的成員。
未命名的命名空間中定義的名字只在包含該命名空間的文件中可見。如果另一文件包含一個未命名的命名空間,兩個命名空間不相關。兩個命名空間可以定義相同的名字,而這些定義將引用不同的實體。
未命名空間中定義的名字可以在定義該命名空間所在的作用域中找到。如果在文件的最外層作用域中定義未命名的命名空間,那么,未命名的空間中的名字必須與全局作用域中定義的名字不同:
int?i;???//?global?declaration?for?i?????namespace?{
?????????int?i;
?????}
?????//?error:?ambiguous?defined?globally?and?in?an?unnested,?unnamed?namespace
?????i?=?10;
?
像任意其他命名空間一樣,未命名的命名空間也可以嵌套在另一命名空間內部。如果未命名的命名空間是嵌套的,其中的名字按常規方法使用外圍命名空間名字訪問:
namespace?local?{????????namespace?{
????????????int?i;
????????}
?????}
????????//?ok:?i?defined?in?a?nested?unnamed?namespace?is?distinct?from?global?i
????????local::i?=?42;
?
在標準 C++ 中引入命名空間之前,程序必須將名字聲明為 static,使它們局部于一個文件。文件中靜態聲明的使用從 C 語言繼承而來,在 C 語言中,聲明為 static 的局部實體在聲明它的文件之外不可見。
????C++ 不贊成文件靜態聲明。不造成的特征是在未來版本中可能不支持的特征。應該避免文件靜態而使用未命名空間代替。
?
14. 多重繼承與虛繼承
在多重繼承下,派生類的對象包含每個基類的基類子對象。
虛繼承來解決菱形繼承中的多個基類子對象的問題。
在 C++ 中,通過使用虛繼承解決這類問題。虛繼承是一種機制,類通過虛繼承指出它希望共享其虛基類的狀態。在虛繼承下,對給定虛基類,無論該類在派生層次中作為虛基類出現多少次,只繼承一個共享的基類子對象。共享的基類子對象稱為虛基類。
虛繼承帶來了初始化順序的問題。
通常,每個類只初始化自己的直接基類。在應用于虛基類的進修,這個初始化策略會失敗。如果使用常規規則,就可能會多次初始化虛基類。類將沿著包含該虛基類的每個繼承路徑初始化。
為了解決這個重復初始化問題,從具有虛基類的類繼承的類對初始化進行特殊處理。在虛派生中,由最低層派生類的構造函數初始化虛基類。
構造函數與析構函數次序:無論虛基類出現在繼承層次中任何地方,總是在構造非虛基類之前構造虛基類。
代碼如下:
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
?
運行結果:
第18章 特殊工具和技術
1. 優化內存分配
C++ 的內存分配是一種類型化操作:new為特定類型分配內存,并在新分配的內存中構造該類型的一個對象。new 表達式自動運行合適的構造函數來初始化每個動態分配的類類型對象。
new 基于每個對象分配內存的事實可能會對某些類強加不可接受的運行時開銷,這樣的類可能需要使用用戶級的類類型對象分配能夠更快一些。這樣的類使用的通用策略是,預先分配用于創建新對象的內存,需要時在預先分配的內存中構造每個新對象。
另外一些類希望按最小尺寸為自己的數據成員分配需要的內存。例如,標準庫中的 vector 類預先分配額外內存以保存加入的附加元素,將新元素加入到這個保留容量中。將元素保持在連續內存中的時候,預先分配的元素使 vector 能夠高效地加入元素。
在每種情況下(預先分配內存以保存用戶級對象或者保存類的內部數據)都需要將內存分配與對象構造分離開。將內存分配與對象構造分離開的明顯的理由是,在預先分配的內存中構造對象很浪費,可能會創建從不使用的對象。當實際使用預先分配的對象的時候,被使用的對象必須重新賦以新值。更微妙的是,如果預先分配的內存必須被構造,某些類就不能使用它。例如,考慮 vector,它使用了預先分配策略。如果必須構造預先分配的內存中的對象,就不能有基類型為沒有默認構造函數的 vector——vector 沒有辦法知道怎樣構造這些對象。
2. C++中的內存分配
C++ 中,內存分配和對象構造緊密糾纏,就像對象和內存回收一樣。使用 new 表達式的時候,分配內存,并在該內存中構造一個對象;使用 delete 表達式的時候,調用析構函數撤銷對象,并將對象所用內存返還給系統。
接管內存分配時,必須處理這兩個任務。分配原始內存時,必須在該內存中構造對象;在釋放該內存之前,必須保證適當地撤銷這些對象。
C++ 提供下面兩種方法分配和釋放未構造的原始內存。
(1).allocator 類,它提供可感知類型的內存分配。這個類支持一個抽象接口,以分配內存并隨后使用該內存保存對象。
(2).標準庫中的 operator new 和 operator delete,它們分配和釋放需要大小的原始的、未類型化的內存。
C++ 還提供不同的方法在原始內存中構造和撤銷對象。
(1).allocator 類定義了名為 construct 和 destroy 的成員,其操作正如它們的名字所指出的那樣:construct 成員在未構造內存中初始化對象,destroy 成員在對象上運行適當的析構函數。
(2).定位 new 表達式(placement new expression)接受指向未構造內存的指針,并在該空間中初始化一個對象或一個數組。
(3).可以直接調用對象的析構函數來撤銷對象。運行析構函數并不釋放對象所在的內存。
(4).算法 uninitialized_fill 和 uninitialized_copy 像 fill 和 copy 算法一樣執行,除了它們的目的地構造對象而不是給對象賦值之外。
3. allocator類
allocator 類將內存分配和對象構造分開。當 allocator 對象分配內存的時候,它分配適當大小并排列成保存給定類型對象的空間。但是,它分配的內存是未構造的,allocator 的用戶必須分別 construct 和 destroy 放置在該內存中的對象。
回憶一下,vector 類將元素保存在連續的存儲中。為了獲得可接受的性能,vector 預先分配比所需元素更多的元素。每個將元素加到容器中的 vector 成員檢查是否有可用空間以容納另一元素。如果有,該成員在預分配內存中下一可用位置初始化一個對象;如果沒有自由元素,就重新分配 vector:vector 獲取新的空間,將現在元素復制到空間,增加新元素,并釋放舊空間。
vector 所用存儲開始是未構造內存,它還沒有保存任何對象。將元素復制或增加到這個預分配空間的時候,必須使用 allocator 類的 construct 成員構造元素。
?
為了說明這些概念,我們將實現 vector 的一小部分。將我們的類命名為 Vector,以區別于標準類 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
?????????//?...
?????};
每個 Vector<T> 類型定義一個 allocator<T> 類型的 static 數據成員,以便在給定類型的 Vector 中分配和構造元素。每個 Vector 對象在指定類型的內置數組中保存其元素,并維持該數組的下列三個指針:
- elements,指向數組的第一個元素。
- first_free,指向最后一個實際元素之后的那個元素。
- end,指向數組本身之后的那個元素。
可以使用這些指針來確定 Vector 的大小和容量:
- Vector 的 size(實際使用的元素的數目)等于 first_free-elements。
- Vector 的 capacity(在必須重新分配 Vector 之前,可以定義的元素的總數)等于end-elements。
- 自由空間(在需要重新分配之前,可以增加的元素的數目)是 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 函數首先確定是否有可用空間,如果沒有,就調用 reallocate 函數,reallocate 分配新空間并復制現存元素,將指針重置為指向新分配的空間。
一旦 push_back 函數知道還有空間容納新元素,它就請求 allocator 對象構造一個新的最后元素。construct 函數使用類型 T 的復制構造函數將 t 值復制到由 first_free 指出的元素,然后,將 first_free 加 1 以指出又有一個元素在用。
?
reallocate 函數所做的工作最多:
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;
?????}
?
我們使用一個簡單但效果驚人的策略:每次重新分配時分配兩倍內存。函數首先計算當前在用的元素數目,將該數目翻倍,并請求 allocator 對象來獲得所需數量的空間。如果 Vector 為空,就分配兩個元素。
如果 Vector 保存 int 值,allocate 函數調用為 newcapacity 數目的 int 值分配空間;如果 Vector 保存 string 對象,它就為給定數目的 string 對象分配空間。
uninitialized_copy 調用使用標準 copy 算法的特殊版本。這個版本希望目的地是原始的未構造內存,它在目的地復制構造每個元素,而不是將輸入范圍的元素賦值給目的地,使用 T 的復制構造函數從輸入范圍將每個元素復制到目的地。
for 循環對舊數組中每個對象調用 allocator 的 destroy 成員它按逆序撤銷元素,從數組中最后一個元素開始,以第一個元素結束。destroy 函數運行 T 類型的析構函數來釋放舊元素所用的任何資源。
一旦復制和撤銷了元素,就釋放原來數組所用的空間。在調用 deallocate 之前,必須檢查 elements 是否實際指向一個數組。
最后,必須重置指針以指向新分配并初始化的數組。將 first_free 和 end 指針分別置為指向最后構造的元素之后的單元以及所分配空間末尾的下一單元。
?
完整的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表達式的工作原理:
當使用 new 表達式
//?new?expressionstring?*?sp?=?new?string("initialized");
的時候,實際上發生三個步驟。首先,該表達式調用名為 operator new 的標準庫函數,分配足夠大的原始的未類型化的內存,以保存指定類型的一個對象;接下來,運行該類型的一個構造函數,用指定初始化式構造對象;最后,返回指向新分配并構造的對象的指針。
當使用 delete 表達式
delete?sp;?
刪除動態分配對象的時候,發生兩個步驟。首先,對 sp 指向的對象運行適當的析構函數;然后,通過調用名為 operator delete 的標準庫函數釋放該對象所用內存。
?
5. new表達式與operator new函數
標準庫函數 operator new 和 operator delete 的命名容易讓人誤解。與其他 operator 函數(如 operator=)不同,這些函數沒有重載 new 或 delete 表達式,實際上,我們不能重定義 new 和 delete 表達式的行為。
通過調用 operator new 函數執行 new 表達式獲得內存,并接著在該內存中構造一個對象,通過撤銷一個對象執行 delete 表達式,并接著調用 operator delete 函數,以釋放該對象使用的內存。
?
6. operator new 函數和 operator delete 函數
(1) operator new 和 operator delete 接口如下:
operator new 和 operator delete 函數有兩個重載版本,每個版本支持相關的 new 表達式和 delete 表達式:
void?*operator?new(size_t);?//?allocate?an?objectvoid?*operator?new[](size_t);?//?allocate?an?array
void?*operator?delete(void*);?//?free?an?object
void?*operator?delete[](void*);?//?free?an?array
(2)使用分配操作符函數
雖然 operator new 和 operator delete 函數的設計意圖是供 new 表達式使用,但它們通常是標準庫中的可用函數。可以使用它們獲得未構造內存,它們有點類似 allocate 類的 allocator 和 deallocate 成員。例如,代替使用 allocator 對象,可以在 Vector 類中使用 operator new 和 operator delete 函數。在分配新空間時我們曾編寫
//?allocate?space?to?hold?newcapacity?number?of?elements?of?type?TT*?newelements?=?alloc.allocate(newcapacity);
這可以重新編寫為
//?allocate?unconstructed?memory?to?hold?newcapacity?elements?of?type?TT*?newelements?=?static_cast<T*>(operator?new[](newcapacity?*?sizeof(T)));
?
類似地,在重新分配由 Vector 成員 elements 指向的舊空間的時候,我們曾經編寫
//?return?the?memory?that?held?the?elementsalloc.deallocate(elements,?end?-?elements);
?
這可以重新編寫為
//?deallocate?the?memory?that?they?occupiedoperator?delete[](elements);
?
這些函數的表現與 allocate 類的 allocator 和 deallocate 成員類似。但是,它們在一個重要方面有不同:它們在 void* 指針而不是類型化的指針上進行操作。
一般而言,使用 allocator 比直接使用 operator new 和 operator delete 函數更為類型安全。
allocate 成員分配類型化的內存,所以使用它的程序可以不必計算以字節為單位的所需內存量,它們也可以避免對 operator new 的返回值進行強制類型轉換。類似地,deallocate 釋放特定類型的內存,也不必轉換為 void*。
?
7. 定位new表達式
標準庫函數 operator new 和 operator delete 是 allocator 的 allocate 和 deallocate 成員的低級版本,它們都分配但不初始化內存。
allocator 的成員 construct 和 destroy 也有兩個低級選擇,這些成員在由 allocator 對象分配的空間中初始化和撤銷對象。
類似于 construct 成員,有第三種 new 表達式,稱為定位 new。定位 new 表達式在已分配的原始內存中初始化一個對象,它與 new 的其他版本的不同之處在于,它不分配內存。相反,它接受指向已分配但未構造內存的指針,并在該內存中初始化一個對象。實際上,定位 new 表達式使我們能夠在特定的、預分配的內存地址構造一個對象。
?
定位 new 表達式的形式是:
new (place_address) type
new (place_address) type (initializer-list)
其中 place_address 必須是一個指針,而 initializer-list 提供了(可能為空的)初始化列表,以便在構造新分配的對象時使用。
?
可以使用定位 new 表達式代替 Vector 實現中的 construct 調用。原來的代碼
//?construct?a?copy?t?in?the?element?to?which?first_free?pointsalloc.construct?(first_free,?t);
?
可以用等價的定位 new 表達式代替
//?copy?t?into?element?addressed?by?first_freenew?(first_free)?T(t);
?
定位 new 表達式比 allocator 類的 construct 成員更靈活。定位 new 表達式初始化一個對象的時候,它可以使用任何構造函數,并直接建立對象。construct 函數總是使用復制構造函數。
例如,可以用下面兩種方式之一,從一對迭代器初始化一個已分配但未構造的 string 對象:
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 表達式使用了接受一對迭代器的 string 構造函數,在 sp 指向的空間直接構造 string 對象。當調用 construct 函數的時候,必須首先從迭代器構造一個 string 對象,以獲得傳遞給 construct 的 string 對象,然后,該函數使用 string 的復制構造函數,將那個未命名的臨時 string 對象復制到 sp 指向的對象中。
?
通常,這些區別是不相干的:對值型類而言,在適當的位置直接構造對象與構造臨時對象并進行復制之間沒有可觀察到的區別,而且性能差別基本沒有意義。但對某些類而言,使用復制構造函數是不可能的(因為復制構造函數是私有的),或者是應該避免的,在這種情況下,也許有必要使用定位 new 表達式。
8. 顯示析構函數的調用
正如定位 new 表達式是使用 allocate 類的 construct 成員的低級選擇,我們可以使用析構函數的顯式調用作為調用 destroy 函數的低級選擇。
在使用 allocator 對象的 Vector 版本中,通過調用 destroy 函數清除每個元素:
//?destroy?the?old?elements?in?reverse?orderfor?(T?*p?=?first_free;?p?!=?elements;?/*?empty?*/?)
alloc.destroy(--p);
?
對于使用定位 new 表達式構造對象的程序,顯式調用析構函數:
for?(T?*p?=?first_free;?p?!=?elements;?/*?empty?*/?)p->~T();?//?call?the?destructor
?
在這里直接調用析構函數。箭頭操作符對迭代器 p 解引用以獲得 p 所指的對象,然后,調用析構函數,析構函數以類名前加 ~ 來命名。
顯式調用析構函數的效果是適當地清除對象本身。但是,并沒有釋放對象所占的內存,如果需要,可以重用該內存空間。
?
9. 運行時類型識別
通過運行時類型識別(RTTI),程序能夠使用基類的指針或引用來檢索這些指針或引用所指對象的實際派生類型。
通過下面兩個操作符提供 RTTI:
(1) typeid 操作符,返回指針或引用所指對象的實際類型。
(2) dynamic_cast 操作符,將基類類型的指針或引用安全地轉換為派生類型的指針或引用。
這些操作符只為帶有一個或多個虛函數的類返回動態類型信息,對于其他類型,返回靜態(即編譯時)類型的信息。
對于帶虛函數的類,在運行時執行 RTTI 操作符,但對于其他類型,在編譯時計算 RTTI 操作符。
當具有基類的引用或指針,但需要執行不是基類組成部分的派生類操作的時候,需要動態的強制類型轉換。通常,從基類指針獲得派生類行為最好的方法是通過虛函數。當使用虛函數的時候,編譯器自動根據對象的實際類型選擇正確的函數。
但是,在某些情況下,不可能使用虛函數。在這些情況下,RTTI 提供了可選的機制。然而,這種機制比使用虛函數更容易出錯:程序員必須知道應該將對象強制轉換為哪種類型,并且必須檢查轉換是否成功執行了。
????使用動態強制類型轉換要小心。只要有可能,定義和使用虛函數比直接接管類型管理好得多。
?
10. dynamic_cast操作符
可以使用 dynamic_cast 操作符將基類類型對象的引用或指針轉換為同一繼承層次中其他類型的引用或指針。與 dynamic_cast 一起使用的指針必須是有效的——它必須為 0 或者指向一個對象。
與其他強制類型轉換不同,dynamic_cast 涉及運行時類型檢查。如果綁定到引用或指針的對象不是目標類型的對象,則 dynamic_cast 失敗。如果轉換到指針類型的 dynamic_cast 失敗,則 dynamic_cast 的結果是 0 值;如果轉換到引用類型的 dynamic_cast 失敗,則拋出一個 bad_cast 類型的異常。
因此,dynamic_cast 操作符一次執行兩個操作。它首先驗證被請求的轉換是否有效,只有轉換有效,操作符才實際進行轉換。一般而言,引用或指針所綁定的對象的類型在編譯時是未知的,基類的指針可以賦值為指向派生類對象,同樣,基類的引用也可以用派生類對象初始化,因此,dynamic_cast 操作符執行的驗證必須在運行時進行。
作為例子,假定 Base 是至少帶一個虛函數的類,并且 Derived 類派生于 Base 類。如果有一個名為 basePtr 的指向 Base 的指針,就可以像這樣在運行時將它強制轉換為指向 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 將基類指針轉換為派生類指針,也可以使用 dynamic_cast 將基類引用轉換為派生類引用,這種 dynamic_cast 操作的形式如下:
dynamic_cast< Type& >(val)
這里,Type 是轉換的目標類型,而 val 是基類類型的對象。
只有當 val 實際引用一個 Type 類型對象,或者 val 是一個 Type 派生類型的對象的時候,dynamic_cast 操作才將操作數 val 轉換為想要的 Type& 類型。
?
因為不存在空引用,所以不可能對引用使用用于指針強制類型轉換的檢查策略,相反,當轉換失敗的時候,它拋出一個 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代替虛函數
12. typeid操作符
如果表達式的類型是類類型且該類包含一個或多個虛函數,則表達式的動態類型可能不同于它的靜態編譯時類型。例如,如果表達式對基類指針解引用,則該表達式的靜態編譯時類型是基類類型;但是,如果指針實際指向派生類對象,則 typeid 操作符將說表達式的類型是派生類型。
typeid 操作符可以與任何類型的表達式一起使用。內置類型的表達式以及常量都可以用作 typeid 操作符的操作數。如果操作數不是類類型或者是沒有虛函數的類,則 typeid 操作符指出操作數的靜態類型;如果操作數是定義了至少一個虛函數的類類型,則在運行時計算類型。
typeid 操作符的結果是名為 type_info 的標準庫類型的對象引用,第 18.2.4 節將更詳細地討論這個類型。要使用 type_info 類,必須包含庫頭文件 typeinfo。
typeid 最常見的用途是比較兩個表達式的類型,或者將表達式的類型與特定類型相比較:
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 類隨編譯器而變。一些編譯器提供附加的成員函數,那些函數提供關于程序中所用類型的附加信息。你應該查閱編譯器的參考手冊來理解所提供的確切的 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. 類成員指針
可以通過使用稱為成員指針的特殊各類的指針做到這一點。成員指針包含類的類型以及成員的類型。這一事實影響著怎樣定義成員指針,怎樣將成員指針綁定到函數或數據成員,以及怎樣使用它們。
成員指針只應用于類的非 static 成員。static 類成員不是任何對象的組成部分,所以不需要特殊語法來指向 static 成員,static 成員指針是普通指針。
成員函數的指針必須在三個方面與它所指函數的類型相匹配:
(1)函數形參的類型和數目,包括成員是否為 const。
(2)返回類型。
(3)所屬類的類型。
通過指定函數返回類型、形參表和類來定義成員函數的指針。
普通指針與成員指針
15. 類成員指針的使用
類似于成員訪問操作符 . 和 ->,.* 和 -> 是兩個新的操作符,它們使我們能夠將成員指針綁定到實際對象。這兩個操作符的左操作數必須是類類型的對象或類類型的指針,右操作數是該類型的成員指針。
(1) 成員指針解引用操作符(.*)從對象或引用獲取成員。
(2) 成員指針箭頭操作符(->*)通過對象的指針獲取成員。
?
16. 聯合Union
聯合是一種特殊的類。一個 union 對象可以有多個數據成員,但在任何時刻,只有一個成員可以有值。當將一個值賦給 union 對象的一個成員的時候,其他所有都變為未定義的。
(1)沒有靜態數據成員、引用成員或類數據成員
某些(但不是全部)類特征同樣適用于 union。例如,像任何類一樣,union 可以指定保護標記使成員成為公用的、私有的或受保護的。默認情況下,union 表現得像 struct:除非另外指定,否則 union 的成員都為 public 成員。
union 也可以定義成員函數,包括構造函數和析構函數。但是,union 不能作為基類使用,所以成員函數不能為虛數。
union 不能具有靜態數據成員或引用成員,而且,union 不能具有定義了構造函數、析構函數或賦值操作符的類類型的成員:
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
?????};
?
這個限制包括了具有帶構造函數、析構函數或賦值操作符的成員的類。
(2)嵌套聯合,匿名聯合
union 最經常用作嵌套類型,其中判別式是外圍類的一個成員:
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
?????};
?
這個類中,用枚舉對象 tok 指出 val 成員中存儲了哪種值,val 成員是一個(未命名的)union,它保存 char、int 或 double 值。
經常使用 switch 語句(第 6.6 節)測試判別式,然后根據 union 中當前存儲的值進行處理:
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;
?????}
?
不用于定義對象的未命名 union 稱為匿名聯合。匿名 union 的成員的名字出現在外圍作用域中。例如,使用匿名 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;
?????????};
?????};
?
因為匿名 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)位域
可以聲明一種特殊的類數據成員,稱為位域,來保存特定的位數。當程序需要將二進制數據傳遞給另一程序或硬件設備的時候,通常使用位域。
位域必須是整型數據類型,可以是 signed 或 unsigned。通過在成員名后面接一個冒號以及指定位數的常量表達式,指出成員是一個位域:
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限定符
直接處理硬件的程序常具有這樣的數據成員,它們的值由程序本身直接控制之外的過程所控制。例如,程序可以包含由系統時鐘更新的變量。當可以用編譯器的控制或檢測之外的方式改變對象值的時候,應該將對象聲明為 volatile。關鍵字 volatile 是給編譯器的指示,指出對這樣的對象不應該執行優化。
用與 const 限定符相同的方式使用 volatile 限定符。volatile 限定符是一個對類型的附加修飾符:
volatile?int?display_register;?????volatile?Task?*curr_task;
?????volatile?int?ixa[max_size];
?????volatile?Screen?bitmap_buf;
?
?
第 4.2.5 節介紹了 const 限定符與指針的相互作用,volatile 限定符與指針之間也存在同樣的相互作用。可以聲明 volatile 指針、指向 volatile 對象的指針,以及指向 volatile 對象的 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 對象的地址賦給指向 volatile 的指針,或者將指向 volatile 類型的指針復制給指向 volatile 的指針。只有當引用為 volatile 時,我們才可以使用 volatile 對象對引用進行初始化。
?
對待 const 和 volatile 的一個重要區別是,不能使用合成的復制和賦值操作符從 volatile 對象進行初始化或賦值。合成的復制控制成員接受 const 形參,這些形參是對類類型的 const 引用,但是,不能將 volatile 對象傳遞給普通引用或 const 引用。
如果類希望允許復制 volatile 對象,或者,類希望允許從 volatile 操作數或對 volatile 操作數進行賦值,它必須定義自己的復制構造函數和/或賦值操作符版本:
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
?????};
?
通過將復制控制成員的形參定義為 const volatile 引用,我們可以從任何各類的 Foo 對象進行復制或賦值:普通 Foo 對象、const Foo 對象、volatile Foo 對象或 const volatile Foo 對象。
????
雖然可以定義復制控制成員來處理 volatile 對象,但更深入的問題是復制 volatile 對象是否有意義,對該問題的回答與任意特定程序中使用 volatile 的原因密切相關。
?
(3) 鏈接指示:extern "c"
鏈接指示與函數重載之間的相互作用依賴于目標語言。如果語言支持重載函數,則為該語言實現鏈接指示的編譯器很可能也支持 C++ 的這些函數的重載。
C++ 保證支持的唯一語言是 C。C 語言不支持函數重載,所以,不應該對下面的情況感到驚訝:在一組重載函數中只能為一個 C 函數指定鏈接指示。用帶給定名字的 C 鏈接聲明多于一個函數是錯誤的:
//?error:?two?extern?"C"?functions?in?set?of?overloaded?functionsextern?"C"?void?print(const?char*);
extern?"C"?void?print(int);
在 C++ 程序中,重載 C 函數很常見,但是,重載集合中的其他函數必須都是 C++ 函數:
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++ 程序調用 calc 的 C 版本。其余函數是帶類型形參的 C++ 函數,只能從 C++ 程序調用。聲明的次序不重要。
編寫函數所用的語言是函數類型的一部分。為了聲明用其他程序設計語言編寫的函數的指針,必須使用鏈接指示:
//?pf?points?to?a?C?function?returning?void?taking?an?intextern?"C"?void?(*pf)(int);
使用 pf 調用函數的時候,假定該調用是一個 C 函數調用而編譯該函數。
????C 函數的指針與 C++ 函數的指針具有不同的類型,不能將 C 函數的指針初始化或賦值為 C++ 函數的指針(反之亦然)。
存在這種不匹配的時候,會給出編譯時錯誤:
void?(*pf1)(int);?//?points?to?a?C++?functionextern?"C"?void?(*pf2)(int);?//?points?to?a?C?function
pf1?=?pf2;?//?error:?pf1?and?pf2?have?different?types
?
一些 C++ 編譯器可以接受前面的賦值作為語言擴展,盡管嚴格說來它是非法的。
?
?
轉載于:https://www.cnblogs.com/xkfz007/archive/2012/08/15/2639509.html
總結
以上是生活随笔為你收集整理的读书笔记之:C++ Primer (第4版)及习题(ch12-ch18) [++++]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Visual Studio服务器控件被警
- 下一篇: 根据文件路径生成相应文件