1. 程式人生 > >C++“準”標準庫Boost學習指南(9):Boost.Bind

C++“準”標準庫Boost學習指南(9):Boost.Bind

Boost.Bind

Bind是對標準庫的繫結器bind1st 和 bind2nd的泛化。這個庫支援使用統一的語法將引數繫結到任何類似於函式行為的東西,如函式指標、函式物件,以及成員函式指標。它還可以通過巢狀繫結器實現函式組合。這個庫不要求那些對標準庫繫結器的強制約束,最顯著的就是不要求你的類提供typedefs result_type, first_argument_type, 和 second_argument_type 等。這個庫也使得我們不再需要用 ptr_fun, mem_fun, 和 mem_fun_ref 等介面卡。它是對C++標準庫的一個重要且很有用的擴充。Bind可以被標準庫的演算法使用,也經常用於Boost的函式,它提供了一個強大的工具,用於存放後續呼叫的函式和函式物件。Bind 已被即將釋出的Library Technical Report所接受。

Bind 庫如何改進你的程式?

  • 使函式和函式物件適用於標準庫演算法
  • 使用一致語法建立繫結器
  • 強大的函式組合

在使用來自於標準庫的演算法時,你常常需要提供給它們一個函式或一個函式物件。這是對演算法的行為進行定製的一個好方法,但你通常需要寫一個新的函式物件,因為你沒有組合函式或改變引數的順序等所需的工具。雖然標準庫已經提供了一些可用的工具,如 bind1st 和 bind2nd, 但是這不夠用。即使功能上夠用了,但這通常意味著要忍受笨拙的語法,這些語法通常會讓不熟悉這些工具的程式設計師產生混亂。你需要的是一個解決方案,既具備所需功能,又可以使用普通的語法就地建立函式物件,這正是 Boost.Bind 所要做的。

事實上,泛型繫結器是一種 lambda 表示式,因為通過函式組合,我們可以或多或少在呼叫點構造一個區域性的、無名的函式。在許多情形下這都是需要的,因為它達到了三個目的:減少了程式碼的數量,使程式碼更易懂,還有行為的區域性化,這意味著更有效的維護。注意,還有另一個 Boost 庫,Boost.Lambda, 它具有更多的特性。Boost.Lambda 將在下一章中討論。為什麼你不直接跳到下一個庫?因為多數情況下,Boost.Bind 可以完成你要繫結的所有東西,並且學習曲線沒那麼陡。

Bind 成功的一個關鍵是採用統一的語法來建立函式物件,以及對於使用該庫的型別只有很少的要求。這種設計使得無需關注如何去寫與你的型別一起工作的程式碼,而只需關注我們最關心的一點,程式碼如何工作以及它實際上做了什麼。使用來自標準庫的介面卡時,如 ptr_fun 和 mem_fun_ref, 程式碼很容易變得過分冗長,因為我們必須提供這些介面卡以便引數可以符合演算法的要求。在 Boost.Bind 裡不是這樣的,它使用了更為精妙的推斷系統,並且在自動推斷不能適用時提供了一個簡單的語法。使用 Bind 的結果就是,你可以寫更少的程式碼,而且程式碼更易懂。

Bind 如何適用於標準庫?


概念上,Bind  是已有的標準庫函式 bind1st 和 bind2nd 的泛化,其額外的功能就是允許更為精妙的函式組合。它還減少了對函式指標和類成員指標使用介面卡的需要,從而縮短了程式碼,也減少了出錯的機會。Boost.Bind 還包含了對C++標準庫的一些常用的擴充,如SGI擴充的 compose1 和 compose2, 還有 select1st 和 select2nd 函式。因此,Bind 非常適用於標準庫,而且它也真的非常好用。這些功能被公認為是需要的,最終將被引入到標準庫中,也是對STL的擴充套件。Boost.Bind 已經被即將釋出的 Library Technical Report 所接納。

Bind

標頭檔案: "boost/bind.hpp"

Bind 庫建立函式物件來繫結到一個函式(普通函式或成員函式)。不需要直接給出函式的所有引數,引數可以稍後給,這意味著繫結器可以用於建立一個改變了它所繫結到的函式的 arity (引數數量) 的函式物件,或者按照你喜歡的順序重排引數。

函式 bind 的過載版本的返回型別是未指定的,即不能保證返回的函式物件的特徵是怎樣的。有時,你需要將物件存於某處,而不是直接把它傳送給另一個函式,這時,你要使用 Boost.Function, 後面的部分中討論。弄明白 bind 函式返回的是什麼的關鍵在於,理解它發生了什麼轉換。用 bind 函式的一個過載,template<class R, class F> unspecified-1 bind(F f)來作為例子,返回型別就是 (引用自線上文件),"一個函式物件 l ,表示式 l(v1, v2, ..., vm) 等同於 f(),隱式轉換為 R"。這樣,這個被繫結的函式就被儲存在繫結器裡面,以後對這個函式物件的呼叫就會得到被繫結的函式的返回值(如果有),即模板引數 R. 我們在這討論的實現支援最多九個函式引數。

Bind 的實現包括許多函式和類,但作為使用者來說,我們不直接使用除了過載函式 bind 以外的任何東西。所有繫結通過 bind 函式發生,我們可以無須依賴於返回值的型別。使用 bind 時,引數佔位符(命名為 _1, _2, 等等)不需要用一個using宣告或using指示來引入,因為它們位於匿名名字空間。這樣,在使用 Boost.Bind時,沒有理由寫出以下的程式碼。

  1. using boost::bind;
  2. using namespace boost;
前面曾經提到過,當前的 Boost.Bind 實現支援九個佔位符(_1, _2, _3, 等等),也就是說最多九個引數。粗略地過一下大綱對於深入理解如何進行型別推斷是有好處的,還可以知道何時/為何它不總是可以工作的。花點時間分析一下成員函式指標與普通函式的署名特徵也是很有用的。你將會看到對於普通函式和類成員函式,各有各的過載版本。還有,對於每一個數量的引數,也都有不同的過載。我不在這裡列出所有大綱了,建議你到www.boost.org參考一下 Boost.Bind 的文件。

用法

Boost.Bind 為函式和函式物件提供了一致的語法,對於值語義和指標語義也一樣。我們將從一些簡單的例子開始,處理一些簡單繫結的用法,然後再轉移到通過巢狀繫結進行函式組合。弄明白如何使用 bind 的關鍵是,佔位符的概念。佔位符用於表示提供給結果函式物件的引數,Boost.Bind 支援最多九個引數。佔位符被命名為 _1, _2, _3, _4, 直至 _9, 你要把它們放在你原先放參數的地方。作為第一個例子,我們定義一個函式,nine_arguments, 它將被一個 bind 表示式呼叫。

#include <iostream>
#include "boost/bind.hpp"

void nine_arguments(
  int i1,int i2,int i3,int i4,
  int i5,int i6,int i7,int i8, int i9) {
  std::cout << i1 << i2 << i3 << i4 << i5
    << i6 << i7 << i8 << i9 << '\n';
}

int main() {
  int i1=1,i2=2,i3=3,i4=4,i5=5,i6=6,i7=7,i8=8,i9=9;
  (boost::bind(&nine_arguments,_9,_2,_1,_6,_3,_8,_4,_5,_7))
    (i1,i2,i3,i4,i5,i6,i7,i8,i9);
}


在這個例子中,你建立了一個匿名臨時繫結器,並立即把引數傳遞給它的呼叫操作符來呼叫它。如你所見,佔位符的順序是被攪亂的,這說明引數的順序被重新安排了。注意,佔位符可以在一個表示式中被多次使用。這個程式的輸出如下。
921638457

這表示了佔位符對應於它的數字所示位置的引數,即 _1 被第一個引數替換,_2 被第二個引數替換,等等。接下來,你將看到如何呼叫一個類的成員函式。

呼叫成員函式

我們來看一下如何用 bind 呼叫成員函式。我們先來做一些可以用標準庫來做的事情,這樣可以對比一下用 Boost.Bind 的方法。儲存某種型別的元素在一個標準庫容器中,一個常見的需要是對某些或全部元素呼叫一個成員函式。這可以用一個迴圈來完成,通常也正是這樣做的,但還有更好的方法。考慮下面這個簡單的類,status, 我們將用它來示範 Boost.Bind 的易用性和強大的功能。
class status {
  std::string name_;
  bool ok_;
public:
  status(const std::string& name):name_(name),ok_(true) {}

  void break_it() {
    ok_=false;
  }

  bool is_broken() const {
    return ok_;
  }

  void report() const {
    std::cout << name_ << " is " <<
      (ok_ ? "working nominally":"terribly broken") << '\n';
  }
};


如果我們把這個類的例項儲存在一個 vector, 並且我們需要呼叫成員函式 report, 我們可能會象下面這樣做。
std::vector<status> statuses;
statuses.push_back(status("status 1"));
statuses.push_back(status("status 2"));
statuses.push_back(status("status 3"));
statuses.push_back(status("status 4"));

statuses[1].break_it();
statuses[2].break_it();

for (std::vector<status>::iterator it=statuses.begin();
  it!=statuses.end();++it) {
  it->report();
}


這個迴圈正確地完成了任務,但它是冗長、低效的(由於要多次呼叫 statuses.end()),並且不象使用標準庫演算法 for_each 那樣清楚地表明意圖。為了用 for_each 來替換這個迴圈,我們需要用一個介面卡來對 vector 元素呼叫成員函式 report 。這時,由於元素是以值的方式儲存的,我們需要的是介面卡 mem_fun_ref.
std::for_each(
  statuses.begin(),
  statuses.end(),
  std::mem_fun_ref(&status::report));


這是一個正確、合理的方法,它非常簡潔,非常清楚這段程式碼是幹什麼的。以下是使用 Boost.Bind 完成相同任務的程式碼。
std::for_each(
  statuses.begin(),
  statuses.end(),
  boost::bind(&status::report,_1));


這個版本同樣的清楚、明白。這是前面所說的佔位符的第一個真正的使用,我們同時告訴編譯器和程式碼的讀者,_1 用於替換這個函式所呼叫的繫結器的第一個實際引數。雖然這段程式碼節省了幾個字元,但在這種情況下標準庫的 mem_fun_ref 和 bind 之間並沒有太大的不同,但是讓我們來重用這個例子並把容器改為儲存指標。
std::vector<status*> p_statuses;
p_statuses.push_back(new status("status 1"));
p_statuses.push_back(new status("status 2"));
p_statuses.push_back(new status("status 3"));
p_statuses.push_back(new status("status 4"));

p_statuses[1]->break_it();
p_statuses[2]->break_it();


我們還可以使用標準庫,但不能再用 mem_fun_ref. 我們需要的是介面卡 mem_fun, 它被認為有點用詞不當,但它的確正確完成了需要做的工作。
std::for_each(
  p_statuses.begin(),
  p_statuses.end(),
  std::mem_fun(&status::report));


雖然這也可以工作,但語法變了,即使我們想做的事情非常相似。如果語法可以與第一個例子相同,那就更好了,所以我們所關心的是程式碼要做什麼,而不是如何去做。使用 bind, 我們就無須關心我們處理的元素是指標了(這一點已經在容器型別的宣告中表明瞭,對於現代的庫來說,這樣的冗餘資訊是不需要的)。
std::for_each(
  p_statuses.begin(),
  p_statuses.end(),
  boost::bind(&status::report,_1));


如你所見,這與我們前一個例子完全一樣,這意味著如果我們之前已經明白了 bind ,那麼我們現在也清楚它。現在,我們已決定換用指標了,我們要面對另一個問題,即生存期控制。我們必須手工釋放 p_statuses 中的元素,這很容易出錯,也無須如此。所以,我們可能決定開始使用智慧指標,並(再次)修改我們的程式碼。
std::vector<boost::shared_ptr<status> > s_statuses;
s_statuses.push_back(
  boost::shared_ptr<status>(new status("status 1")));
s_statuses.push_back(
  boost::shared_ptr<status>(new status("status 2")));
s_statuses.push_back(
  boost::shared_ptr<status>(new status("status 3")));
s_statuses.push_back(
  boost::shared_ptr<status>(new status("status 4")));
s_statuses[1]->break_it();
s_statuses[2]->break_it();


現在,我們要用標準庫中的哪個介面卡呢?mem_fun 和 mem_fun_ref 都不適用,因為智慧指標沒有一個名為 report 的成員函式,所以以下程式碼編譯失敗。
std::for_each(
  s_statuses.begin(),
  s_statuses.end(),
  std::mem_fun(&status::report));


不巧,標準庫不能幫我們完成這個任務。因此,我們不得不採用我們正想要擺脫的迴圈,或者使用 Boost.Bind, 它不會抱怨任何事情,而且正確地完成我們想要的。
std::for_each(
  s_statuses.begin(),
  s_statuses.end(),
  boost::bind(&status::report,_1));


再一次,這段程式碼與前面的例子完全一樣(除了容器的名字不同)。使用繫結的語法是一致的,不論是用於值語義或是指標語義,甚至是用於智慧指標。有時,使用不同的語法有助於理解程式碼,但在這裡,不是這樣的,我們的任務是對容器中的元素呼叫成員函式,沒有更多的也沒有更少的事情。語法一致的價值不應被低估,因為它對於編寫程式碼的人,以及對於日後需要維護程式碼的人都是有幫助的(當然,我們並不真的是在寫需要維護的程式碼,但為了這個主題,讓我們假裝是在寫)。

這些例子示範了一個非常基本和常見的情形,在這種情形下 Boost.Bind 尤為出色。即使標準庫也提供了完成相同工作的一些基本工具,但我們還是看到 Bind 既提供了一致的語法,也增加了標準庫目前缺少的功能。

看一下門簾的後面

在你開始使用 Boost.Bind 後,這是無可避免的;你將開始驚訝它到底是如何工作的。這看起來就象是魔術,bind 可以推斷出引數的型別和返回型別,它又是如何處理佔位符的呢?我們將快速地看一下驅動這個東西的機制。它有助於知道一點 bind的工作原理,特別是在試圖解釋這驚人的簡潔性以及編譯器對最輕微的錯誤給出的直接的錯誤資訊。我們將建立一個非常簡單的繫結器,至少是部分地模仿 Boost.Bind 的語法。為了避免把這個離題的討論搞成幾頁那麼長,我們只支援一類繫結,即接受單個引數的成員函式。此外,我們不會對cv限定符進行處理;我們只處理最簡單的情況。

首先,我們需要能夠推斷出我們要繫結的函式的返回型別、類的型別、和引數型別。我們用一個函式模板來做到這一點。
template <typename R, typename T, typename Arg>
simple_bind_t<R,T,Arg> simple_bind(
  R (T::*fn)(Arg),
  const T& t,
  const placeholder&) {
  return simple_bind_t<R,T,Arg>(fn,t);
}


這看起來有點可怕,畢竟這只是在定義整個機器的一部分。但是,這一部分的焦點在於型別推斷在哪發生。你會注意到這個函式有三個模板引數,R, T, 和 Arg. R 是返回的型別,T 是類的型別,而 Arg 是(單個)引數的型別。這些模板引數組成了我們的函式的第一個引數,即 R (T::*f)(Arg). 這樣,傳遞一個帶單個引數的成員函式給 simple_bind 將允許編譯器推斷出 R 為成員函式的返回型別,T 為成員函式的類,Arg 為成員函式的引數型別。simple_bind 的返回型別是一個函式物件,它使用與 simple_bind 相同的三個型別進行特化,其建構函式接受一個成員函式指標和一個對應類(T)的例項。 simple_bind 簡單地忽略佔位符(即函式的最後一個引數),我保留這個引數的原因是為了模仿 Boost.Bind 的語法。在一個更好的實現中,我們顯然應該使用這個引數,但是現在讓我們先不要管它。這個函式物件的實現相當簡單。
template <typename R,typename T, typename Arg>
class simple_bind_t {
  typedef R (T::*fn)(Arg);
  fn fn_;
  T t_;
public:
  simple_bind_t(fn f,const T& t):fn_(f),t_(t) {}

  R operator()(Arg& a) {
    return (t_.*fn_)(a);
  }
};


從 simple_bind 的實現中我們可以看到,建構函式接受兩個引數:第一個是指向成員函式的指標,第二個是一個 const T 引用,它會被複制並稍後用於給定一個使用者提供的引數來呼叫其成員函式。最後,呼叫操作符返回 R, 即成員函式的返回型別,並接受一個 Arg 引數,即傳給成員函式的那個引數的型別。呼叫成員函式的語法稍稍有點晦澀:
  1. (t_.*fn_)(a);
.* 是成員指標操作符,它的第一個運算元是 class T; 另外還有一個成員指標操作符,->*, 它的第一個運算元是是一個 T 指標。剩下就是建立一個佔位符,即用於替換實際引數的變數。我們可以通過在匿名名字空間中包含某種型別的變數來建立一個佔位符;我們把它稱為 placeholder:
namespace {
  class placeholder {};
  placeholder _1;
}


我們建立一個簡單的類和一個小程式來測試一下。
class Test {
public:
  void do_stuff(const std::vector<int>& v) {
    std::copy(v.begin(),v.end(),
      std::ostream_iterator<int>(std::cout," "));
  }
};

int main() {
  Test t;
  std::vector<int> vec;
  vec.push_back(42);
  simple_bind(&Test::do_stuff,t,_1)(vec);
}


當我們用上述引數例項化函式 simple_bind 時,型別被自動推斷;R 是 void, T 是 Test, 而 Arg 是一個 const std::vector<int> 引用。函式返回一個 simple_bind_t<void,Test,Arg> 的例項,我們立即呼叫它的呼叫操作符,並傳進一個引數 vec.

非常不錯,simple_bind 已經給了你關於繫結器如何工作的一些想法。現在,是時候回到 Boost.Bind 了!

關於佔位符和引數

第一個例子示範了 bind 最多可以支援九個引數,但瞭解多一點關於引數和佔位符如何工作的情況,可以讓我們更好地使用它。首先,很重要的一點是,普通函式與成員函式之間有著非常大的差異,在繫結一個成員函式時,bind 表示式的第一個引數必須是成員函式所在類的例項!理解這個規則的最容易的方法是,這個顯式的引數將取替隱式的 this ,被傳遞給所有的非靜態成員函式。細心的讀者將會留意到,實際上這意味著對於成員函式的繫結器來說,只能支援八個引數,因為第一個要用於傳遞實際的物件。以下例子定義了一個普通函式 print_string 和一個帶有成員函式 print_string 的類 some_class ,它們將被用於 bind 表示式。
#include <iostream>
#include <string>
#include "boost/bind.hpp"

class some_class {
public:
  typedef void result_type;
  void print_string(const std::string& s) const {
    std::cout << s << '\n';
  }
};

void print_string(const std::string s) {
  std::cout << s << '\n';
}

int main() {
  (boost::bind(&print_string,_1))("Hello func!");
  some_class sc;
  (boost::bind(&some_class::print_string,_1,_2))
    (sc,"Hello member!");
}


第一個 bind 表示式繫結到普通函式 print_string. 因為該函式要求一個引數,因此我們需要用一個佔位符(_1)來告訴 bind 它的哪一個引數將被傳遞為 print_string 的第一個引數。要呼叫獲得的函式物件,我們必須傳遞一個 string 引數給呼叫操作符。引數是一個 const std::string&, 因此傳遞一個字面的字串將引發一個 std::string 轉型建構函式的呼叫。
  1. (boost::bind(&print_string,_1))("Hello func!");
第二個繫結器用於一個成員函式,some_class 的 print_string 。bind 的第一個引數是成員函式指標。但是,一個非靜態成員函式指標並不真的是一個指標。我們必須要有一個物件才可以呼叫這個函式。這就是為什麼這個 bind 表示式必須宣告繫結器有兩個引數,呼叫它時兩個引數都必須提供。
  1. boost::bind(&some_class::print_string,_1,_2);
要看看為什麼會這樣,就要考慮一下得到的這個函式物件要怎麼使用。我們必須把一個 some_class 例項和一個 print_string 用的引數一起傳遞給它。
  1. (boost::bind(&some_class::print_string,_1,_2))(sc,"Hello member!");
這個呼叫操作符的第一個引數是 this ,即那個 some_class 例項。注意,這第一個引數可以是一個指標(智慧的或裸的)或者是一個引用;bind 是非常隨和的。呼叫操作符的第二個引數是那個成員函式要用的引數。這裡,我們"延遲"了所有兩個引數,即我們定義的這個繫結器,它的兩個引數,物件本身及成員函式的引數,都要在呼叫操作符時才指定。我們不是一定非這樣做不可。例如,我們可以建立一個繫結器,每次呼叫它時,都是對同一個物件呼叫 print_string ,就象這樣:
  1. (boost::bind(&some_class::print_string,some_class(),_1))
  2. ("Hello member!");
這次得到的函式物件已經包含了一個 some_class 例項,因此它的呼叫操作符只需要一個佔位符(_1)和一個引數(一個string)。最後,我們還可以建立一個所謂的無參(nullary)函式,它連那個 string 也綁定了,就象這樣:
  1. (boost::bind(&some_class::print_string,
  2. some_class(),"Hello member!"))();
這些例子清楚地顯示了 bind 的多功能性。它可用於延遲它所封裝的函式的所有引數、部分引數、或一個引數也不延遲。它也可以把引數按照你所要的順序進行重排;只要照你的需要排列佔位符就行了。接下來,我們將看看如何用 bind 來就地建立排序用的謂詞。

動態的排序標準

在對容器中的元素進行排序時,我們有時候需要建立一個函式物件以定義排序的標準,如果我們沒有提供關係操作符,或者是已有的關係操作符不是我們想要的排序標準時,就需要這樣做了。有些時候我們可以使用來自標準庫的比較函式物件(std::greater, std::greater_equal, 等等),但只能對已有型別進行比較,我們不能就地定義一個新的。我們將使用一個名為 personal_info 的類來演示 Boost.Bind 如何幫助我們。personal_info 包含有 first name, last name, 和 age, 並且它沒有提供任何的比較操作符。這些資訊在建立以後就不再變動,並且可以用成員函式 name, surname, 和 age 來取出。
class personal_info {
  std::string name_;
  std::string surname_;
  unsigned int age_;

public:
  personal_info(
    const std::string& n,
    const std::string& s,
    unsigned int age):name_(n),surname_(s),age_(age) {}

  std::string name() const {
    return name_;
  }

  std::string surname() const {
    return surname_;
  }

  unsigned int age() const {
    return age_;
  }
};


我們通過提供以下操作符來讓這個類可以流輸出(OutputStreamable):
std::ostream& operator<<(
  std::ostream& os,const personal_info& pi) {
  os << pi.name() << ' ' <<
    pi.surname() << ' ' << pi.age() << '\n';
  return os;
}


如果我們要對含有型別 personal_info 元素的容器進行排序,我們就需要為它提供一個排序謂詞。為什麼開始的時候我們沒有為 personal_info 提供關係操作符呢?一個原因是,因為有幾種排序的可能性,而我們不知道對於不同的使用者哪一種是合適的。雖然我們也可以選擇為不同的排序標準提供不同的成員函式,但這樣會加重負擔,我們要在類中實現所有相關的排序標準,這並不總是可以做到的。幸運的是,我們可以很容易地用 bind 就地建立所需的謂詞。我們先看看基於年齡(可以通過成員函式 age 取得)來進行排序。我們可以為此建立一個函式物件。
class personal_info_age_less_than :
  public std::binary_function<
  personal_info,personal_info,bool> {
public:
  bool operator()(
  const personal_info& p1,const personal_info& p2) {
    return p1.age()<p2.age();
  }
};


我們讓 personal_info_age_less_than 公有派生自 binary_function. 從 binary_function 派生可以提供使用介面卡時所需的 typedef ,例如使用 std::not2. 假設有一個 vector, vec, 含有型別為 personal_info 的元素,我們可以象這樣來使用這個函式物件:
  1. std::sort(vec.begin(),vec.end(),personal_info_age_less_than());
只要不同的比較方式的數量很有限,這種方式就可以工作良好。但是,有一個潛在的問題,計算邏輯被定義在不同的地方,這會使得程式碼難以理解。利用一個較長的、描述清晰的名字可以解決這個問題,就象我們在這裡做的一樣,但是不是所有情況都會這樣清晰,有很大可能我們需要為大於、小於或等於關係提供一堆的函式物件。

那麼,Boost.Bind 有什麼幫助呢?實際上,在這個例子中它可以幫助我們三次。如果我們要解決這個問題,我們發現有三件事情要做,第一件是繫結一個邏輯操作,如 std::less. 這很容易,我們可以得到第一部分程式碼。
  1. boost::bind<bool>(std::less<unsigned int>(),_1,_2);
注意,我們通過把 bool 引數提供給 bind,顯式地給出了返回型別。有時這是需要的,對於有缺陷的編譯器或者在無法推斷出返回型別的上下文時。如果一個函式物件包含 typedef, result_type, 就不需要顯式給出返回型別。現在,我們有了一個接受兩個引數的函式物件,兩個引數的型別都是 unsigned int, 但我們還不能用它,因為容器中的元素的型別是 personal_info, 我們需要從這些元素中取出 age 並把它作為引數傳遞給 std::less. 我們可以再次使用 bind 來實現。
boost::bind(
  std::less<unsigned int>(),
  boost::bind(&personal_info::age,_1),
  boost::bind(&personal_info::age,_2));


這裡,我們建立了另外兩個繫結器。第一個用主繫結器的呼叫操作符的第一個引數(_1)來呼叫 personal_info::age 。第二個用主繫結器的呼叫操作符的第二個引數(_2)來呼叫 personal_info::age 。因為 std::sort 傳遞兩個 personal_info 物件給主繫結器的呼叫操作符,結果就是對來自被排序的 vector 的兩個 personal_info 分別呼叫 personal_info::age 。最後,主繫結器傳遞兩個新的、內層的繫結器的呼叫操作符所返回的 age 給 std::less. 這正是我們所需要的!呼叫這個函式物件的結果就是 std::less 的結果,這意味著我們有了一個有效的比較函式物件可以用來排序容器中的 personal_info 物件。以下是使用它的方法:
std::vector<personal_info> vec;
vec.push_back(personal_info("Little","John",30));
vec.push_back(personal_info("Friar", "Tuck",50));
vec.push_back(personal_info("Robin", "Hood",40));

std::sort(
  vec.begin(),
  vec.end(),
  boost::bind(
    std::less<unsigned int>(),
    boost::bind(&personal_info::age,_1),
    boost::bind(&personal_info::age,_2)));


我們可以簡單地通過繫結另一個 personal_info 成員(變數或函式)來進行不同的排序,例如,按 last name 排序。
std::sort(
  vec.begin(),
  vec.end(),
  boost::bind(
    std::less<std::string>(),
    boost::bind(&personal_info::surname,_1),
    boost::bind(&personal_info::surname,_2)));


這是一種出色的技術,因為它提供了一個重要的性質:就地實現簡單的函式。它使得程式碼易懂且易於維護。雖然技術上可以用繫結器實現基於複雜標準的排序,但那樣做是不明智的。給 bind 表示式新增複雜的邏輯會很快失去它的清晰和簡潔。雖然有時你想用繫結來做更多的事情,但最好是讓繫結器與要維護它的人一樣聰明,而不是更加聰明。

函式組合,Part I

一個常見的問題是,將一些函式或函式物件組合成一個函式物件。假設你需要測試一個 int ,看它是否大於5且小於等於10。使用"常規"的程式碼,你將這樣寫:
if (i>5 && i<=10) {
  // Do something
}


如果是處理一個容器中的元素,上述程式碼只有放在一個單獨的函式時才能工作。如果你不想這樣,那麼用一個巢狀的 bind 也可以獲得相同的效果(注意,這時通常不能使用標準庫的 bind1st 和 bind2nd)。如果我們對這個問題進行分解,我們會發現我們需要:邏輯與(std::logical_and), 大於(std::greater), 和小於等於(std::less_equal)。邏輯與看起來就象這樣:
  1. boost::bind(std::logical_and<bool>(),_1,_2);
然後,我們需要另一個謂詞來回答 _1 是否大於5。
  1. boost::bind(std::greater<int>(),_1,5);
然後,我們還需要另一個謂詞來回答 _1 是否小於等於10。
  1. boost::bind(std::less_equal<int>(),_1,10);
最後,我們需要把它們兩個用邏輯與合起來,就象這樣:
boost::bind(
  std::logical_and<bool>(),
  boost::bind(std::greater<int>(),_1,5),
  boost::bind(std::less_equal<int>(),_1,10));


這樣一個巢狀的 bind 相對容易理解,雖然它是後序的。還有,任何人都可以逐字地閱讀這段程式碼並弄清楚它的意圖。我們用一個例子來測試一下這個繫結器。
std::vector<int> ints;

ints.push_back(7);
ints.push_back(4);
ints.push_back(12);
ints.push_back(10);

int count=std::count_if(
  ints.begin(),
  ints.end(),
  boost::bind(
    std::logical_and<bool>(),
    boost::bind(std::greater<int>(),_1,5),
    boost::bind(std::less_equal<int>(),_1,10)));

std::cout << count << '\n';

std::vector<int>::iterator int_it=std::find_if(
  ints.begin(),
  ints.end(),
  boost::bind(std::logical_and<bool>(),
    boost::bind(std::greater<int>(),_1,5),
    boost::bind(std::less_equal<int>(),_1,10)));

if (int_it!=ints.end()) {
  std::cout << *int_it << '\n';
}


使用巢狀的 bind 時,小心地對程式碼進行正確的縮入非常重要,因為如果一旦縮入錯誤,程式碼就會很難理解。想想前面那段清晰的程式碼,再看看以下這個容易混亂的例子。
std::vector<int>::iterator int_it=
  std::find_if(ints.begin(),ints.end(),
    boost::bind<bool>(
    std::logical_and<bool>(),
    boost::bind<bool>(std::greater<int>(),_1,5),
      boost::bind<bool>(std::less_equal<int>(),_1,10)));


當然,對於較長的程式碼行,這是一個常見的問題,但是在使用這裡所描述的結構時更為明顯,在這裡長語句是合理的而不是個別例外。因此,請對你之後的程式設計師友好些,確保你的程式碼行正確縮入,這樣可以讓人更容易閱讀。

本書的一位認真的審閱者曾經問過,在前面的例子中,為什麼建立了兩個相同的繫結器,而不是建立一個繫結器物件然後使用兩次?答案是,因為我們不知道 bind 所建立的繫結器的精確型別(它是由實現定義的),我們沒有方法為它宣告一個變數。還有,這個型別通常都非常複雜,因為它的署名特徵包括了函式 bind 中所有的型別資訊(自動推斷的)。但是,可以用另外一個工具來儲存得到的函式物件,例如來自 Boost.Function 的工具。相關方法的詳情請見 "Library 11: Function 11"。

這裡給出的函式組合的要點與標準庫的一個著名的擴充相符,即來自SGI STL的函式 compose2 ,它在 Boost.Compose 庫(現在已經不用了)中也被稱為 compose_f_gx_hx 。

函式組合,Part II

在SGI STL中的另一個常用的函式組合是 compose1 ,在 Boost.Compose 中是 compose_f_gx 。這些函式提供了用一個引數呼叫兩個函式的方法,把最裡面的函式返回的結果傳遞給第一個函式。有時一個例子勝過千言萬語,設想你需要對容器中的浮點數元素執行兩個算術操作。我們首先把值增加10%,然後再減少10%;這個例子對於少數工作在財政部門的人來說可能是有用的一課。
std::list<double> values;
values.push_back(10.0);
values.push_back(100.0);
values.push_back(1000.0);

std::transform(
  values.begin(),
  values.end(),
  values.begin(),
  boost::bind(
    std::multiplies<double>(),0.90,
    boost::bind<double>(
      std::multiplies<double>(),_1,1.10)));

std::copy(
  values.begin(),
  values.end(),
  std::ostream_iterator<double>(std::cout," "));


你怎麼知道哪個巢狀的 bind 先被呼叫呢?你也許已經注意到,總是最裡面的 bind 先被求值。這意味著我們可以把同樣的程式碼寫得稍微有點不同。
std::transform(
  values.begin(),
  values.end(),
  values.begin(),
  boost::bind<double>(
    std::multiplies<double>(),
    boost::bind<double>(
      std::multiplies<double>(),_1,1.10),0.90));


這裡,我們改變了傳給 bind 的引數的順序,把第一個 bind 的引數加在了表示式的最後。雖然我不建議這樣做,但它對於理解引數如何傳遞給 bind 函式很有幫助。
bind 表示式中的是值語義還是指標語義?

當我們傳遞某種型別的例項給一個 bind 表示式時,它將被複制,除非我們顯式地告訴 bind 不要複製它。要看我們怎麼做,這可能是至關重要的。為了看一下在我們背後發生了什麼事情,我們建立一個 tracer 類,它可以告訴我們它什麼時候被預設構造、被複制構造、被賦值,以及被析構。這樣,我們就可以很容易看到用不同的方式使用 bind 會如何影響我們傳送的例項。以下是完整的 tracer 類。
class tracer {
public:
  tracer() {
    std::cout << "tracer::tracer()\n";
  }

  tracer(const tracer& other) {
    std::cout << "tracer::tracer(const tracer& other)\n";
  }

  tracer& operator=(const tracer& other) {
    std::cout <<
      "tracer& tracer::operator=(const tracer& other)\n";
    return *this;
  }

  ~tracer() {
    std::cout << "tracer::~tracer()\n";
  }

  void print(const std::string& s) const {
    std::cout << s << '\n';
  }
};


我們把我們的 tracer 類用於一個普通的 bind 表示式,象下面這樣。
tracer t;
boost::bind(&tracer::print,t,_1)
  (std::string("I'm called on a copy of t\n"));
執行這段程式碼將產生以下輸出,可以清楚地看到有很多拷貝產生。
tracer::tracer()
tracer::tracer(const tracer& other)
tracer::tracer(const tracer& other)
tracer::tracer(const tracer& other)
tracer::~tracer()
tracer::tracer(const tracer& other)
tracer::~tracer()
tracer::~tracer()
I'm called on a copy of t

tracer::~tracer()
tracer::~tracer()  // 譯註:原文沒有這一行,有誤


如果我們使用的物件的拷貝動作代價昂貴,我們也許就不能這樣用 bind 了。但是,拷貝還是有優點的。它意味著 bind 表示式以及由它所得到的繫結器不依賴於原始物件(在這裡是 t)的生存期,這通常正是想要的。要避免複製,我們必須告訴 bind 我們想傳遞引用而不是它所假定的傳值。我們要用 boost::ref 和 boost::cref (分別用於引用和 const 引用)來做到這一點,它們也是 Boost.Bind 庫的一部分。對我們的 tracer 類使用 boost::ref ,測試程式碼現在看起來象這樣:
tracer t;
boost::bind(&tracer::print,boost::ref(t),_1)(
  std::string("I'm called directly on t\n"));
Executing the code gives us this:
tracer::tracer()
I'm called directly on t

tracer::~tracer()  // 譯註:原文為 tracer::~tracer,有誤


這正是我們要的,避免了無謂的複製。bind 表示式使用原始的例項,這意味著沒有 tracer 物件的拷貝了。當然,它同時也意味著繫結器現在要依賴於 tracer 例項的生存期了。還有一種避免複製的方法;就是通過指標來傳遞引數而不是通過值來傳遞。
tracer t;
boost::bind(&tracer::print,&t,_1)(
  std::string("I'm called directly on t\n"));


因此說,bind 總是執行復制。如果你通過值來傳遞,物件將被複制,這可能對效能有害或者產生不必要的影響。為了避免複製物件,你可以使用 boost::ref/boost::cref 或者使用指標語義。

虛擬函式也可以繫結

到目前為止,我們看到了 bind如何可以用於非成員函式和非虛擬成員函式,但是它也可以用於繫結一個虛擬成員函式。通過 Boost.Bind, 你可以象使用非虛擬函式一樣使用虛擬函式,即把它繫結到最先宣告該成員函式為虛擬的基類的那個虛擬函式上。這個繫結器就可以用於所有的派生類。如果你繫結到其它派生類,你就限制了可以使用這個繫結器的類。考慮以下兩個類 base 和 derived :
class base {
public:
  virtual void print() const {
    std::cout << "I am base.\n";
  }
  virtual ~base() {}
};

class derived : public base {
public:
  void print() const {
    std::cout << "I am derived.\n";
  }
};


我們可以用這兩個類對繫結到虛擬函式進行測試,如下:
derived d;
base b;
boost::bind(&base::print,_1)(b);
boost::bind(&base::print,_1)(d);


執行這段程式碼可以清楚地看到結果正是我們所希望的。
I am base.
I am derived.

對於可以支援虛擬函式,你應該不會驚訝,現在我們已經示範了它和其它函式一樣執行。有一個相關的注意事項,如果你 bind 了一個成員函式而後來它被一個派生類重新定義了,或者一個虛擬函式在基類中是公有的而在派生類中變成了私有的,那麼會發生什麼呢?還可以正常工作嗎?如果可以,你希望是哪一種行為呢?是的,不管你是否使用 Boost.Bind,行為都不會有變化。因面,如果你 bind到一個在其它類中被重新定義的函式,即它不是虛擬的並且派生類有一個相同特徵的成員函式,那麼基類中的版本將被呼叫。如果函式被隱藏,繫結器依然會被執行,因為它顯式地訪問型別中的函式,這樣即使是被隱藏的成員函式也可以使用。最後,如果虛擬函式在基類中宣告為公有的,但在派生類中變成了私有的,那麼對一個派生類例項呼叫該函式將會成功,因為訪問是通過一個基類例項產生的,而基類的成員函式是公有的。當然,這種情況顯示出設計的確是有問題的。

繫結到成員變數

很多時候你需要 bind 資料成員而不是成員函式。例如,使用 std::map 或 std::multimap 時,元素的型別是 std::pair<key const,data>, 但你想使用的資訊通常不是 key, 而是 data. 假設你想把一個 map 中的每個元素傳遞給一個函式,它接受單個 data 型別的引數。你需要建立一個繫結器,它把每個元素(型別為 std::pair)的 second 成員傳給繫結的函式。以下程式碼舉例說明如何實現:
void print_string(const std::string& s) {
  std::cout << s << '\n';
}

std::map<int,std::string> my_map;
my_map[0]="Boost";
my_map[1]="Bind";

std::for_each(
  my_map.begin(),
  my_map.end(),
  boost::bind(&print_string, boost::bind(
    &std::map<int,std::string>::value_type::second,_1)));


你可以 bind 到一個成員變數,就象你可以繫結一個成員函式或普通函式一樣。要注意的是,要使得程式碼更易讀(和寫),使用短的、方便的名字是個好主意。在前例中,對 std::map 使用一個 typedef 有助於提高可讀性。
  1. typedef std::map<int,std::string> map_type;
  2. boost::bind(&map_type::value_type::second,_1)));
雖然需要 bind 到成員變數的時候沒有象成員函式那麼多,但是可以這樣做還是很方便的。SGI STL (及其派生的庫)的使用者可能很熟悉 select1st 和 select2nd 函式。它們用於選出 std::pair 的 first 或 second 成員,與我們在這個例子中所做的一樣。注意,bind 可以用於任意型別和任意名字。

繫結還是不繫結

Boost.Bind 庫帶來了很大的靈活性,但是也給程式設計師帶來了挑戰,因為有些時候本應該使用獨立的函式物件的,但也會讓人傾向於使用繫結器。許多工作可以也應該利用 Bind 來完成,但過度使用也是一種錯誤,應該在程式碼開始變得難以閱讀、理解和維護的地方畫一條分界線。不幸的是,分界線的位置是由分享(閱讀、維護和擴充套件)程式碼的程式設計師所決定的,他們的經驗決定了什麼是可以接受的,什麼不是。使用專門的函式物件的好處是,它們通常是無需加以說明的,而使用繫結器來提供同樣清楚的資訊則是一項我們必須堅持克服的挑戰。例如,如果你需要建立一個你都很難弄明白的巢狀 bind ,有可能就是你已經過度使用了。讓我們用程式碼來解釋一下。
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include "boost/bind.hpp"

void print(std::ostream* os,int i) {
  (*os) << i << '\n';
}

int main() {
  std::map<std::string,std::vector<int> > m;
  m["Strange?"].push_back(1);
  m["Strange?"].push_back(2);
  m["Strange?"].push_back(3);
  m["Weird?"].push_back(4);
  m["Weird?"].push_back(5);

  std::for_each(m.begin(),m.end(),
    boost::bind(&print,&std::cout,
      boost::bind(&std::vector<int>::size,
        boost::bind(
          &std::map<std::string,
            std::vector<int> >::value_type::second,_1))));
}


上面這段程式碼實際上做了什麼?有的人可以流暢地閱讀這段程式碼,但對於我們多數人來說,需要一些時間才能搞清楚它是幹嘛的。是的,繫結器對 pair (即 std::map<std::string,std::vector<int> >::value_type)的成員 second 呼叫成員函式 size 。這種情況下,簡單的問題被繫結器弄得複雜了,建立一個小的函式物件來取代這個讓人難以理解的複雜繫結器是更好的選擇。一個可以完成相同工作的簡單函式物件如下:
class print_size {
  std::ostream& os_;
  typedef std::map<std::string,std::vector<int> > map_type;
public:
  print_size(std::ostream& os):os_(os) {}

  void operator()(
    const map_type::value_type& x) const {
    os_ << x.second.size() << '\n';
  }
};


這種時候使用函式物件的最大好處就是,名字是無需加以說明的。
  1. std::for_each(m.begin(),m.end(),print_size(std::cout));
我們把這些(函式物件以及實際呼叫的所有程式碼)和前面使用繫結器的版本作一下比較。
std::for_each(m.begin(),m.end(),
  boost::bind(&print,&std::cout,
    boost::bind(&std::vector<int>::size,
      boost::bind(
        &std::map<std::string,
          std::vector<int> >::value_type::second,_1))));


或者,如果我們負點責任,為 vector 和 map 分別建立一個簡潔的 typedef :
std::for_each(m.begin(),m.end(),
  boost::bind(&print,&std::cout,
    boost::bind(&vec_type::size,
      boost::bind(&map_type::value_type::second,_1))));


這樣可以容易點分析,但它還是有點長。

雖然使用 bind 版本是有一些好理由,但我想觀點是很清楚的,繫結器不是非用不可的工具,使用時應該負責任,要讓它們物有所值。這一點在使用標準庫的容器和演算法時非常、非常普遍。當事情變得太過複雜時,就回到老風格的方法上。

讓繫結器把握狀態

建立一個象 print_size 那樣的函式物件時,有幾個選項可用。我們在上一節中建立的那個版本中,儲存了一個到 std::ostream 的引用,並使用這個 ostream 來列印 map_type::value_type 引數的成員 second 的 size 函式的返回值。以下是原來的 print_size :
class print_size {
  std::ostream& os_;
  typedef std::map<std::string,std::vector<int> > map_type;
public:
  print_size(std::ostream& os):os_(os) {}

  void operator()(
    const map_type::value_type& x) const {
    os_ << x.second.size() << '\n';
 }
};


要重點關注的一點是,這個類是有狀態的,狀態就在於那個儲存的 std::ostream. 我們可以通過向呼叫操作符增加一個 ostream 引數來去掉這個狀態。這意味著這個函式物件將變為無狀態的。
class print_size {
  typedef std::map<std::string,std::vector<int> > map_type;
public:
  typedef void result_type;
  result_type operator()(std::ostream& os,
    const map_type::value_type& x) const {
    os << x.second.size() << '\n';
  }
};


注意,這個版本的 print_size 可以很好地用於 bind, 因為它增加了一個 result_type typedef. 這樣使用者在使用 bind 時就不需要顯式宣告函式物件的返回型別。在這個新版本的 print_size 裡,使用者需要傳遞一個 ostream 引數來呼叫它。這在使用繫結器時是很容易的。用這個新的 print_size 重寫前節中的例子,我們可以得到:
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include "boost/bind.hpp"

// 省略 print_size 的定義

int main() {
  typedef std::map<std::string,std::vector<int> > map_type;
  map_type m;
  m["Strange?"].push_back(1);
  m["Strange?"].push_back(2);
  m["Strange?"].push_back(3);
  m["Weird?"].push_back(4);
  m["Weird?"].push_back(5);

  std::for_each(m.begin(),m.end(),
    boost::bind(print_size(),boost::ref(std::cout),_1));
}


細心的讀者可能覺得為什麼 print_size 不是一個普通函式,畢竟它已經不帶有任何狀態了。事實上,它可以是普通函式。
void print_size(std::ostream& os,
  const std::map<std::string,std::vector<int> >::value_type& x) {
  os << x.second.size() << '\n';
}


還有更多的泛化工作可以做。我們當前版本的 print_size 要求其呼叫操作符的第二個引數是一個 const std::map<std::string,std::vector<int> > 引用,這不夠通用。我們可以做得更好一些,讓呼叫操作符對這個型別進行泛化。這樣,print_size 就可以使用任意型別的引數,只要該引數含有名為 second 的公有成員,並且該成員有一個成員函式 size. 以下是改進後的版本:
class print_size {
public:
  typedef void result_type;
  template <typename Pair> result_type operator()
    (std::ostream& os,const Pair& x) const {
    os << x.second.size() << '\n';
  }
};


這個版本的用法與前一個是一樣的,但它更為靈活。在建立可用於 bind 表示式的函式物件時,這種泛化更為重要。因為這樣的函式物件可用的情形將顯著增加,多數潛在的泛化都是值得做的。既然如此,我們還可以進一步放鬆對使用 print_size 的型別的要求。當前版本的 print_size 要求呼叫操作符的第二個引數是一個類似於 pair 的物件,即一個含有名為 second 的成員的物件。如果我們決定只要求這個引數含有成員函式 size, 這個函式物件就真的與它的名字相符了。
class print_size {
public:
  typedef void result_type;
  template <typename T> void operator()
    (std::ostream& os,const T& x) const {
    os << x.size() << '\n';
  }
};


當然,儘管 print_size 現在是與它的名字相符了,但是我們也要求使用者要做的更多了。象對於我們前面的例子,就需要手工繫結一個
map_type::value_type::second.
std::for_each(m.begin(),m.end(),
  boost::bind(print_size(),boost::ref(std::cout),
    boost::bind(&map_type::value_type::second,_1)));


在使用 bind 時,通常都需要這樣的折衷,泛化只能到此為止,不要損害到可用性。如果我們走到極端,甚至去掉對成員函式 size 的要求,那麼我們就轉了一圈,回到了我們開始的地方,又回到那個對多數程式設計師而言都過於複雜的 bind 表示式了。
std::for_each(m.begin(),m.end(),
  boost::bind(&print,&std::cout,
    boost::bind(&vec_type::size,
      boost::bind(&map_type::value_type::second,_1))));

Bind 總結

在以下情形時使用 Bind :
  • 你需要繫結一個呼叫到一個普通函式,使用部分或全部引數
  • 你需要繫結一個呼叫到一個成員函式,使用部分或全部引數
  • 你需要巢狀組合函式物件

泛化繫結器的存在對於編寫簡潔、連貫的程式碼非常有用。它減少了為了適配函式/函式物件以及函式組合而建立的小函式物件的數量。雖然標準庫已經提供了 Boost.Bind 的一小部分功能,但是 Boost.Bind 所具有的重大改進使得它在多數情況下成為了更好的選擇。除了對已有功能進行簡化外,Bind 還提供了強大的函式組合功能,這為程式設計師提供了強大的力量而且沒有維護上的負作用。如果你已經花了時間學習 bind1st, bind2nd, ptr_fun, mem_fun_ref, 等等,那麼轉換到 Boost.Bind 對你而言幾乎沒有困難。如果你已經開始使用C++標準庫所提供的繫結器,我強烈建議你開始使用 Bind, 因為它更容易學習,而且更強大。

我知道許多程式設計師通常都有繫結器的經驗,特別是函式組合。如果你用過其中之一,我希望本章能夠為你提供某些動力,推動你更進一步。此外,回想一下這種就地宣告並定義的函式意味著什麼,它意味著無需維護。僅僅為了提供正確的署名和執行簡單的小任務而在類的周圍建立一堆小的、看起來很簡單的函式物件,會導致程式碼的分散,與之相比,使用繫結器更容易。

Boost.Bind 庫由 Peter Dimov 建立並維護,他除了令這個庫實現了完整的繫結和函式組合功能之外,還令它可以很好地工作在多數編譯器環境下。

相關推薦

C++“標準Boost學習指南(9)Boost.Bind

Boost.Bind Bind是對標準庫的繫結器bind1st 和 bind2nd的泛化。這個庫支援使用統一的語法將引數繫結到任何類似於函式行為的東西,如函式指標、函式物件,以及成員函式指標。它還可以通過巢狀繫結器實現函式組合。這個庫不要求那些對標準庫繫結器的強制約束,最顯

C++“標準Boost學習指南(2)Boost.Conversion

原文地址 Conversion庫包含有一些函式,它們是現有的強制型別轉換操作符(static_cast, const_cast, 和  dynamic_cast)的增強。Conversion為安全的多型轉換增加了 polymorphic_cast 和 polymorphic

C++“標準Boost學習指南(3)Boost.Utility

Boost.Utility 一些本不應在一個庫裡出現的有用的東西,只是因為它們每個都不太複雜和廣泛,不足夠形成一個單獨的庫。但不是說它們沒有什麼用外;事實上小的工具通常都有最廣泛的用處。在Boost, 這些小工具被集中起來,形成一個稱為Utility的庫。你可以在這找到ch

C++“標準Boost學習指南(1)智慧指標Boost.smart_ptr

我們學習C++都知道智慧指標,例如STL中的std::auto_ptr,但是為什麼要使用智慧指標,使用它能帶給我們什麼好處呢? 最簡單的使用智慧指標可以不會因為忘記delete指標而造成記憶體洩露。還有如果我們開發或者使用第三方的lib中的某些函式需要返回指標,這樣的返回的

一本超越期待的 C++ 書——簡評《Boost程式完全開發指南深入C++“標準

賴勇浩(http://laiyonghao.com) 作為一個時不時要用一點 C++ 的程式設計師,我常常自嘲為斯德哥爾摩綜合症患者,用 Python 寫著懶散的程式碼時,會懷念以前編寫 C++ 程式碼的那種被虐感。但當真正要寫一些 C++ 程式碼的時候,又會懷念 Pytho

c++ 標準學習(一) -- 持續更新

環境搭建---網上查詢 本人測試環境Ubuntu16.04 #include <iostream> #include <boost/version.hpp> #include <boost/config.hpp> using n

C++之Boost標準配置

下載安裝 進入官網下載地址:https://www.boost.org/users/download/ 本教程直接下載官方已編譯庫,不涉及原始碼手動編譯 點選官方編號好的連結,然後進入一個下載地址:https://sourceforge.net/projects/boost/files/boost-

sqlite學習筆記9C語言中使用sqlite之插入數據

name article void num mes cut cpp content int 前面創建了一張表,如今給他插入一些數據。插入數據跟創建表差點兒相同,不過SQL語言不一樣而已,完整代碼例如以下: #include <stdio.h> #inclu

C語言標準

span oca baidu linu math.h mit signal url math   共15個,請查看,在linux下的目錄位/usr/share/include assert.h ctype.h errno.h float.h limits.h

c++11 標準函數 std::move 和 完美轉發 std::forward

標準庫函數 這樣的 除了 值引用 sin 引入 語言 優先 ace c++11 標準庫函數 std::move 和 完美轉發 std::forward #define _CRT_SECURE_NO_WARNINGS #include <iostream>

c++ 常用標準

struct friend 開始 lac 適用於 repl 函數 數組 常用 vector: 在vc6中,如果要鑲嵌使用vector, 如vector<vector<int> >, 後面的兩個> 應該用,空格隔開, 否則被編譯器認為是移位符 s

C++Primer_Chap17_標準特殊設施_List03_正則表示式_筆記

  正則表示式(regular expression)是一種描述字元序列的方法,是一種及其強大的計算工具。C++正則表示式庫(RE庫)定義在標頭檔案regex中,包含多個元件: 正則表示式庫元件 regex 表示有一個正則

C++Primer_Chap17_標準特殊設施_List02_bitset型別_筆記

  定義在標頭檔案bitset中的bitset類使位運算的使用更為容器,並且能夠處理超過最長整型型別大小的位集合。 #include <bitset> 定義和初始化bitset   當我們定義一個bitset時,需要宣告它包含多少個二進位制位。 bit

C++Primer_Chap17_標準特殊設施_List01_tuple型別_筆記

  tuple是類似pair的模板。不同tuple型別的成員型別可以不同,但一個tuple可以有任一數量的成員。每個確定的tuple型別的成員數目是固定的。但一個tuple型別的成員數目可以與另一個tuple型別不同。   當我們希望將一些資料組合成單一物件,但又不

[譯]C++17,標準新引入的並行演算法

看到一個介紹 C++17 的系列博文(原文),有十來篇的樣子,覺得挺好,看看有時間能不能都簡單翻譯一下,這是第七篇~ C++17 對 STL 演算法的改動,概念上其實很簡單.標準庫之前有超過100個演算法,內容包括搜尋,計數,區間及元素操作等等.新標準過載了其中69個演算法

[譯]C++17,標準變化的更多細節

看到一個介紹 C++17 的系列博文(原文),有十來篇的樣子,覺得挺好,看看有時間能不能都簡單翻譯一下,這是第四篇~ 之前的文章中我簡單介紹了一些C++17標準庫的新變化,這次我會介紹更多的相關細節. 讓我們首先來看下之前未提到過的新內容. std::byte

[譯]C++17,標準有哪些新變化?

看到一個介紹 C++17 的系列博文(原文),有十來篇的樣子,覺得挺好,看看有時間能不能都簡單翻譯一下,這是第二篇~ C++17 有許多新的標準庫變化,簡單起見,這篇文章只介紹了以下內容:std::string_view,標準模板庫中新新增的並行演算法,新的檔案系統庫,以及

C++ STL標準與泛型程式設計(一)

泛型程式設計,就是使用模板為主要工具來編寫程式。其中沒有太多的面向物件的觀念,不涉及虛擬函式的使用。 使用C++標準庫 C++標準庫:以程式碼形式給出,放於各種標頭檔案( header files )內,經過編譯後才能使用。 所有新式的 headers 內的元件封裝於 namespace

利用C語言標準生成一個真隨機數的方法

        首先需要明確一點的是,計算機系統中生成一個隨機數,需要依賴一個隨機量,這個隨機量稱為隨機數種子。否則生成的就是偽隨機數。隨機數種子的值越多樣化,生成的數就越隨機。通常,隨機數種子從計算機系統外部引入,例如人的操作、ADC採集到的值等。         C語言

C/C++基礎----標準幾個工具tuple,bitset,正則表示式,隨機數,IO

tuple tuple可以有任意多個成員 預設初始化,值初始化 建構函式是explicit,必須直接初始化 make_tuple(v1,v2,…,vn) get<i> (t) 返回第i個數據成員的引用,t是左值則返回左值引用,右值則返回右值引用 tuple_size<tupleType&