青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

洛譯小筑

別來無恙,我的老友…
隨筆 - 45, 文章 - 0, 評論 - 172, 引用 - 0
數(shù)據(jù)加載中……

[ECPP讀書筆記 條目25] 最好不要讓swap拋出異常

swap是一個非常有趣的程序。它起初是作為STL的一部分引入C++的,而后就成為了異常安全編程的一個重要的支柱(參見條目29),同時對于可以自賦值的對象而言它還是一個常用的復(fù)制處理機制。由于swap如此神通廣大,那么以一個恰當(dāng)?shù)姆绞饺崿F(xiàn)它就顯得十分重要了,但是它舉足輕重的地位也決定了實現(xiàn)它并不是一件手到擒來的事情。在本條目中,我們就會針對swap函數(shù)展開探索,逐步掌握如何去駕馭它。

swap函數(shù)的功能是交換兩個對象的值。在默認(rèn)情況下,交換工作是通過標(biāo)準(zhǔn)庫的swap算法完成的。它的標(biāo)準(zhǔn)實現(xiàn)方式就能精確地完成你所期望的工作:

namespace std {

 

  template<typename T>          // std::swap的標(biāo)準(zhǔn)實現(xiàn)

  void swap(T& a, T& b)         // 交換ab的值

  {

    T temp(a);

    a = b;

    b = temp;

  }

 

}

只要你的類型支持復(fù)制(通過拷貝構(gòu)造函數(shù)和拷貝復(fù)制運算符),那么默認(rèn)的swap實現(xiàn)就可以讓兩個該類型的對象互相交換,你不需要專門做任何工作來支持這一功能。

然而,你可能對默認(rèn)的swap實現(xiàn)抱有諸多不滿。它會帶來3次對象復(fù)制工作:a復(fù)制到tempbatempb。對于一些類型來說,這些復(fù)制操作并不都是必需的。對于這些類型來說,默認(rèn)的swap會成為你程序的桎梏。

上述的那種類型大都符合下面的特征:它的主要成分是一個指針,這一指針會指向另一個類型,真實的數(shù)據(jù)包含在另一個類型中。對這一設(shè)計的一種常見的形式是“pimpl慣用法”(pointer to implementation,指向?qū)崿F(xiàn)的指針,參見條目31)。舉例說明,Widget類可以使用這種設(shè)計模式:

class WidgetImpl {                      // 保存Widget的數(shù)據(jù)的類

public:                                 // 細(xì)節(jié)不重要

  ...

 

private:

  int a, b, c;                           // 可能會有很多數(shù)據(jù)

  std::vector<double> v;                 // 復(fù)制它們的代價是很高的!

  ...

};

 

class Widget {                           // 使用pimpl慣用法的類

public:

  Widget(const Widget& rhs);

 

  Widget& operator=(const Widget& rhs)  // 要復(fù)制一個Widget對象,只要

  {                                     // 復(fù)制對應(yīng)的WidgetImpl對象。

   ...                                  // 關(guān)于operator=實現(xiàn)的一般信息

   *pImpl = *(rhs.pImpl);               // 參見條目101112

   ...

  }

 

  ...

 

private:

  WidgetImpl *pImpl;                    // 指向包含當(dāng)前Widget數(shù)據(jù)的對象

};

為了交換兩個Widget對象的值,我們所要做的僅僅是交換他們的pImpl指針,但是默認(rèn)的swap算法是不可能知道這一切的,它不僅會復(fù)制三個Widget對象,同時也會復(fù)制三個Widget對象。這樣做效率太低了。

我們要做的是告訴std::swap當(dāng)交換Widget時,執(zhí)行的交換操作應(yīng)當(dāng)僅僅針對它們內(nèi)部的pImpl指針。有一種精確的說法來描述這一方法:將Widgetstd::swap特化。下面是基本的思想,盡管以這種方式不能通過編譯:

 

namespace std {

 

  template<>                       // TWidget時,

  void swap<Widget>(Widget& a,     // 這是std::swap的一個特化版本

                    Widget& b)     // 這段代碼不能通過編譯

  {

    swap(a.pImpl, b.pImpl);        // 要交換兩個Widget

  }                                // 只需要交換它們的pImpl指針

 

}

程序開端的“template<>”告訴我們這是std::swap的一個完全特化模板,函數(shù)名后面的“<Widget>”告訴我們當(dāng)前的特化針對TWidget的情況。換種說法,當(dāng)一般的swap模板應(yīng)用于Widget時,應(yīng)當(dāng)使用這一具體實現(xiàn)。一般情況下,我們沒有權(quán)限去改動std名字空間內(nèi)部的內(nèi)容,但是我們有權(quán)針對我們自己創(chuàng)建的類型(比如Widget)來完整地特化標(biāo)準(zhǔn)模板(就像swap)。這就是我們所要做的。

然而,就像我說過的,這段代碼是不能通過編譯的。這是因為它嘗試訪問ab內(nèi)部的pImpl指針,但是它們是私有的。我們可以將我們的特化函數(shù)聲明為友元,但這里的規(guī)則有些不同:這里要求我們讓Widget包含一個名為swap的公共成員函數(shù),讓這個swap進行實際的交換工作,然后特化std:swap來調(diào)用這一成員函數(shù)。

class Widget {                     // 同上,

public:                            // 僅添加了一個swap成員函數(shù)

  ...

  void swap(Widget& other)

  {

    using std::swap;               // 本節(jié)后面會解釋為什么這樣聲明

 

    swap(pImpl, other.pImpl);      // 交換pImpl指針來交換Widget

  }

  ...

};

 

namespace std {

 

  template<>                       // 特化的std::swap (已修正)

  void swap<Widget>(Widget& a, Widget& b)

  {

    a.swap(b);                     // 要交換Widget

  }                                // 只要調(diào)用它們的swap成員函數(shù)

 

}

這樣的代碼不僅僅可以通過編譯,而且也與STL容器相協(xié)調(diào),它不僅僅提供了公有的swap成員函數(shù),而且還提供了特化的std::swap來調(diào)用這些成員函數(shù)。

然而,我們不難發(fā)現(xiàn),WidgetWidgetImpl都是類模板,而不是類,似乎我們可以自定義WidgetImpl中保存的數(shù)據(jù)的類型:

template<typename T> class WidgetImpl { ... };

 

template<typename T> class Widget { ... };

將一個swap成員函數(shù)放入Widget中(如果需要,也可以是WidgetImpl)仍然十分簡單,但是我們對std::swap特化時將會遇到問題。下面是我們希望編寫的代碼:

namespace std {

  template<typename T>

  void swap<Widget<T> >(Widget<T>& a, Widget<T>& b)

                                   // 錯誤!非法代碼

  { a.swap(b); }

 

}

這樣的代碼看上去完美無瑕,但是它是非法的。因為其中嘗試對一個函數(shù)模板(std::swap)進行不完全的特化,但是,盡管C++允許對類模板進行不完全特化,而函數(shù)模板就不行了。這一代碼不應(yīng)通過編譯(盡管一些編譯器會錯誤的接受)。

當(dāng)你期望對一個函數(shù)模板進行“不完全特化”時,通常的做法非常簡單,就是添加一個該函數(shù)的重載。代碼可能是下面的樣子:

namespace std {

 

  template<typename T>             // std::swap的一個重載

  void swap(Widget<T>& a, Widget<T>& b)

                                   // (注意swap后邊沒有<...>

  { a.swap(b); }                   // 下文解釋了為什么這樣做不合法

 

}

一般情況下,重載函數(shù)模板是可以的,但是std是一個很特殊的名字空間,它的規(guī)則也是獨特的。對std中的模板進行完全特化是合法的,但是為std添加一個新的模板卻是不合法的(類或函數(shù)或其他一切都不可以)。std的內(nèi)容是由C++標(biāo)準(zhǔn)化委員會一手確定的,我們無法修改他們所規(guī)定的任何形式,只能“望碼興嘆”。越軌的代碼似乎可以運行,但它們的行為卻是未定義的。如果你希望你的代碼擁有可預(yù)知的行為,你就不應(yīng)該在std中添加新的內(nèi)容。

那么應(yīng)該怎么辦呢?我們?nèi)匀恍枰环N方法來讓其他人通過調(diào)用swap來訪問我們更加高效的特化版本。答案很簡單。我們?nèi)匀豢梢酝ㄟ^聲明一個非成員函數(shù)swap來調(diào)用成員函數(shù)swap實現(xiàn),只要這個非成員函數(shù)不是std::swap的特化或者重載版本即可。比如說,如果我們所有與Widget相關(guān)的功能都在名字空間WidgetStuff中,那么代碼看上去應(yīng)該是這樣:

namespace WidgetStuff {

  ...                              // 模板化的WidgetImpl,等等

 

  template<typename T>             // 同上,包括swap成員函數(shù)

  class Widget { ... };

  ...

 

  template<typename T>             // 非成員函數(shù)swap

  void swap(Widget<T>& a, Widget<T>& b)

                                   // 不屬于std名字空間

  {

    a.swap(b);

  }

}

現(xiàn)在,如果任意位置的代碼對兩個Widget對象調(diào)用了swapC++的名字搜尋守則(更具體地說,就是所謂的參數(shù)依賴搜尋或Koenig搜尋)將會在WidgetStuff中查找具體到Widget的版本。這恰恰是我們需要的。

由于這種方法針對類或者類模板可以正常運行,所以看上去似乎我們應(yīng)該在任何情況下都使用它。但是遺憾的是,我們還是要對于類的std::swap進行特化(稍后會交代理由),所以如果你想要在盡可能多的上下文中(你所需要的)調(diào)用具體到類的swap版本,你就需要在你的類所在的名字空間編寫一個非成員版本的swap,同時還需要一個std::swap的特化版本。

順便說一下,即使你沒有使用名字空間,上述內(nèi)容仍然有效(也就是說,你仍需要一個非成員的swap去調(diào)用成員函數(shù)swap),但是為什么你要把所有的類、模板、函數(shù)、枚舉類型、enumeranttypedef的名字統(tǒng)統(tǒng)塞進全局名字空間里呢?如果你對編程規(guī)范有一點概念的話,都不會這樣做的。

到目前為止我所介紹的一切內(nèi)容都是以swap的作者的角度展開的,但是以一個客戶的眼光來審視一下swap也是很有價值的。假設(shè)你正在編寫一個函數(shù)模板,這里你需要交換兩個對象的值:

template<typename T>

void doSomething(T& obj1, T& obj2)

{

  ...

  swap(obj1, obj2);

  ...

}

這里應(yīng)該調(diào)用哪一個swap呢? std中存在一個通用版本,這是你所知道的;另外std中可能還有一個針對這一通用版本的特化版本,它可能存在也可能不存在;或者一個模板的版本,它可能存在也可能不存在,它是否在一個名字空間中也不能確定(但可以肯定不在std名字空間中)?此時你所希望的是,如果存在一個模板版本的話,就調(diào)用它;如果不存在,就返回調(diào)用std中的通用版本。以下是滿足這一要求的代碼:

template<typename T>

void doSomething(T& obj1, T& obj2)

{

  using std::swap;                 // 確保std::swap在此函數(shù)中可用

  ...

  swap(obj1, obj2);                // 為類型T的對象調(diào)用最佳的swap

  ...

}

當(dāng)編譯器看到對swap的調(diào)用時,它們會尋找恰當(dāng)?shù)?span style="font-family:"Courier New";">swap來進行調(diào)用。C++的名字搜尋原則確保了在全局或T類型所在的名字空間中來查找所有的精確到Tswap。(舉例說,如果T是位于WidgetStuff名字空間中的Widget,那么編譯器將會使用參數(shù)依賴搜尋方式來查找WidgetStuff中的swap。)如果沒有精確到Tswap存在,那么編譯器將會使用std中的swap,多虧了using聲明可以使std::swap在本函數(shù)中可見。然而即使這樣,編譯器也更期望得到一個精確到Tstd::swap的特化版本,而不是未確定類型的模板,因此如果std::swap特化為T版本,那么這一特化的版本將會得到使用。

因此,調(diào)用正確的swap十分簡單。你所需要關(guān)心的事僅僅是不去限制對它的調(diào)用,因為如果這樣做會使C++如何決定去調(diào)用函數(shù)的方式受到影響。舉例說,如果你用下面的方式調(diào)用了swap

std::swap(obj1, obj2);             // 調(diào)用swap的錯誤方法

你強迫編譯器僅僅去考慮std中的swap(包括所有的模板特化版本),這樣做就排除了得到一個位于其他位置的精確到T版本的swap的可能,即使它是更加合理的。然而,一些進入誤區(qū)的程序員還是會以這種方式限制swap的調(diào)用,這里你就可以看出,為你的類提供一個std::swap的完全特化版本是多么重要:對于那些使用不恰當(dāng)?shù)木幋a風(fēng)格寫出的代碼(這樣的代碼也存在于一些標(biāo)準(zhǔn)庫的實現(xiàn)當(dāng)中,如果你感興趣可以自己編寫一些代碼,來幫助這樣的代碼盡可能的提高效率),精確到類的swap實現(xiàn)仍然有效。

此刻,我們已經(jīng)介紹了默認(rèn)的swap、成員swap、非成員swapstd::swap的特化版本,以及對swap的調(diào)用,現(xiàn)在讓我們來做一個總結(jié)。

首先,如果對你的類或者類模板使用默認(rèn)的swap實現(xiàn)能夠得到可以接受的效率,你就不需要做任何事情。任何人想要交換你創(chuàng)建的類型的對象時,都會去調(diào)用默認(rèn)的版本,此時可以正常工作。

其次,如果默認(rèn)的swap實現(xiàn)并不夠高效(大多數(shù)情況下意味著你的類或模板正在運用pimpl慣用法),請按下面步驟進行:

1. 提供一個公用的swap成員函數(shù),讓它可以高效的交換你的類型的兩個對象的值。理由將在后面列出,這個函數(shù)永遠不要拋出異常。

2. 在你的類或模板的同一個名字空間中提供一個非成員的swap。讓它調(diào)用你的swap成員函數(shù)。

3. 如果你正在編寫一個類(而不是類模板),要為你的類提供一個std::swap的特化版本。同樣讓它調(diào)用你的swap成員函數(shù)。

最后,如果你正在調(diào)用swap,要確保使用一條using聲明來使std::swap對你的函數(shù)可見,然后在調(diào)用swap時,不要做出任何名字空間的限制。

文中還有一處欠缺,那就是本文的標(biāo)題中的敬告:不要讓swap的成員函數(shù)版本拋出異常。這是因為swap最重要的用途之一就是幫助類(或類模板)來提供異常安全的保證。條目29中詳細(xì)介紹了這一點,但是這一技術(shù)做出了“swap的成員函數(shù)版本永遠不會拋出異常”這一假設(shè)。這一約束僅僅應(yīng)用于成員函數(shù)版本,非成員版本則不受這一限制。這是因為swap的默認(rèn)版本基于拷貝構(gòu)造和拷貝賦值,而在一般情況下,這兩種函數(shù)都可能拋出異常。因此,當(dāng)你編寫一個自定義版本的swap時,在典型情況下你不僅要提供一條更高效的交換對象值的方式,同時你也要提供一個不拋出異常的版本。作為一條一般的守則,這兩條swap的特征是相輔相成的,因為高效的swap同時也基于內(nèi)建數(shù)據(jù)類型的操作(諸如pimpl慣用法中使用的指針),同時內(nèi)建數(shù)據(jù)類型的操作決不會拋出異常。

時刻牢記

在對你的類型使用std::swap時可能會造成效率低下時,可以提供一個swap成員函數(shù)。確保你的swap不要拋出異常。

如果你提供了一個swap的成員函數(shù),那么同時要提供一個非成員函數(shù)swap來調(diào)用這一成員。對于類而言(而不是模板),還要提供一個std::swap的特化版本來調(diào)用swap成員函數(shù)。

在調(diào)用swap時,要為std::swap使用一條using聲明,然后在調(diào)用swap時,不要做出名字空間的限制。

對用戶自定義類型而言,提供std的完全特化版本不成問題,但是決不要嘗試在std中添加全新的內(nèi)容。

posted on 2007-08-02 22:05 ★ROY★ 閱讀(1462) 評論(3)  編輯 收藏 引用 所屬分類: Effective C++

評論

# re: 【讀書筆記】[Effective C++第3版][第25條]最好不要讓交換數(shù)值函數(shù)swap拋出異常  回復(fù)  更多評論   

GOOD.
2007-08-03 00:02 | pass86

# re: 【讀書筆記】[Effective C++第3版][第25條]最好不要讓交換數(shù)值函數(shù)swap拋出異常  回復(fù)  更多評論   

要 up 一下的
2007-08-10 15:29 | 周星星

# re: 【讀書筆記】[Effective C++第3版][第25條]最好不要讓交換數(shù)值函數(shù)swap拋出異常  回復(fù)  更多評論   

汗,這就我就不會了,我只知道裝linux系統(tǒng)一定要有根分區(qū)跟swap
2007-09-02 19:24 | 深藍色的音符
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            日韩一级二级三级| 久久免费午夜影院| 久久久99国产精品免费| 午夜在线观看免费一区| 午夜精品亚洲一区二区三区嫩草| 亚洲视频日本| 欧美一区高清| 奶水喷射视频一区| 亚洲欧洲精品一区二区三区| 亚洲美女91| 亚洲欧美日韩国产综合| 欧美在线观看你懂的| 另类春色校园亚洲| 欧美人与禽猛交乱配视频| 国产精品系列在线| 亚洲国产高清自拍| 亚洲欧美日韩精品在线| 美女精品在线| 在线一区二区三区四区| 欧美一区二区视频免费观看| 欧美α欧美αv大片| 国产精品乱子乱xxxx| 精品不卡在线| 亚洲一区在线免费| 免费一级欧美片在线播放| 一本久久综合| 久久久久国产精品厨房| 欧美日韩亚洲系列| 一区在线播放| 先锋a资源在线看亚洲| 亚洲成色777777在线观看影院| 亚洲视频999| 欧美黄色成人网| 国产一区二区三区在线观看免费 | 亚洲高清自拍| 亚洲男人的天堂在线观看| 鲁大师影院一区二区三区| 亚洲午夜精品久久久久久app| 久久青青草原一区二区| 国产精品一区视频网站| 99热免费精品| 亚洲福利视频免费观看| 欧美一区二区成人6969| 欧美日韩亚洲综合在线| 亚洲国产导航| 久久综合影视| 欧美一区二区三区四区夜夜大片| 欧美日韩成人一区| 亚洲国产成人一区| 久久久亚洲影院你懂的| 亚洲自拍偷拍一区| 国产精品久久777777毛茸茸| 一区二区三区久久精品| 91久久精品国产91性色| 久久综合久久综合久久综合| 国产一区二区三区不卡在线观看| 午夜精品999| 一区二区三区日韩精品视频| 欧美母乳在线| 99re热精品| 亚洲精品日韩精品| 免费视频亚洲| 最新中文字幕一区二区三区| 久热精品视频在线观看| 久久精品久久综合| 国产原创一区二区| 久久中文精品| 麻豆久久婷婷| 亚洲图片欧洲图片日韩av| 一区二区电影免费观看| 国产精品福利在线| 香蕉久久夜色| 久久不射电影网| 激情亚洲成人| 裸体女人亚洲精品一区| 久久久久久久97| 亚洲国产第一页| 亚洲精品国产精品国自产观看| 欧美日本一区二区高清播放视频| 在线一区免费观看| 亚洲一区在线免费| 一区二区亚洲精品| 亚洲精品一区二区网址| 国产老女人精品毛片久久| 另类人畜视频在线| 欧美老女人xx| 欧美在线观看www| 久久久精品一区| 99视频精品在线| 亚洲伊人久久综合| 影音先锋一区| 日韩一级裸体免费视频| 国产女同一区二区| 欧美激情一区二区三区高清视频| 欧美日韩国产一中文字不卡 | 国产欧美日韩另类视频免费观看| 久久国产黑丝| 欧美日本在线一区| 性色av一区二区三区在线观看| 韩国成人福利片在线播放| 在线视频一区观看| 国产日韩欧美在线一区| 欧美风情在线| 国产欧美日韩伦理| 亚洲国产精品va在线观看黑人| 欧美视频日韩视频| 美国十次了思思久久精品导航| 欧美日韩1区2区| 嫩模写真一区二区三区三州| 欧美日一区二区在线观看| 久久亚洲国产成人| 欧美性淫爽ww久久久久无| 欧美大色视频| 国产亚洲人成a一在线v站| 亚洲精品视频一区二区三区| 国内一区二区三区在线视频| 日韩午夜激情| 亚洲日本欧美日韩高观看| 久久精品亚洲一区二区| 亚洲女人小视频在线观看| 欧美成人国产一区二区| 久久久亚洲国产天美传媒修理工| 欧美日韩在线播放三区四区| 欧美国产一区视频在线观看| 国产亚洲视频在线| 亚洲午夜在线| 亚洲一区免费网站| 欧美日韩国产经典色站一区二区三区| 另类春色校园亚洲| 狠狠爱综合网| 久久视频在线免费观看| 久久久久久尹人网香蕉| 国产精品影音先锋| 亚洲综合日韩在线| 亚洲欧美日韩精品久久久| 欧美日韩91| 一区二区三区欧美在线| 午夜精品一区二区三区电影天堂| 国产精品久久国产愉拍| 一本一本久久a久久精品综合麻豆| 亚洲最新色图| 欧美视频导航| 亚洲欧美日韩综合国产aⅴ| 性做久久久久久免费观看欧美| 国产精品视频男人的天堂| 欧美一区二区精品久久911| 久久精品国产91精品亚洲| 国产日韩欧美精品一区| 久久精品国产99国产精品澳门| 久久只有精品| 亚洲美女毛片| 欧美性大战久久久久久久蜜臀| 亚洲一区网站| 久久偷看各类wc女厕嘘嘘偷窃| 一区二区三区在线观看国产| 久久男人资源视频| 亚洲精品国产精品乱码不99按摩| 亚洲手机视频| 激情久久中文字幕| 蜜臀久久久99精品久久久久久 | 99re在线精品| 欧美视频精品在线观看| 亚洲欧美在线免费观看| 免费日韩精品中文字幕视频在线| 亚洲久久一区| 亚洲精品一区二区三区99| 欧美专区在线观看| 国产精品视频你懂的| 久久久91精品国产一区二区精品| 欧美成年人视频网站| 一区二区欧美精品| 国产一区二区成人| 欧美精品七区| 性做久久久久久久免费看| 欧美不卡视频一区发布| 一区二区三区欧美日韩| 国产中文一区二区| 欧美日韩国产精品自在自线| 欧美一区二区在线免费播放| 亚洲全部视频| 久久久久欧美精品| 在线视频日韩精品| 影音先锋日韩资源| 国产精品女主播一区二区三区| 免费成人在线视频网站| 午夜精品免费在线| 日韩视频一区二区三区在线播放免费观看 | 亚洲欧美另类中文字幕| 欧美国产在线电影| 久久久久久久久一区二区| 99精品黄色片免费大全| 韩国女主播一区| 国产精品高潮呻吟久久av黑人| 美国成人直播| 久久av一区二区三区| 亚洲综合日韩| 亚洲视频在线一区观看| 亚洲精品日产精品乱码不卡| 欧美福利视频| 欧美aⅴ99久久黑人专区|