比特幣源碼分析--C++11和boost庫的應用

比特幣源碼分析--C++11和boost庫的應用

    

咱們先停下探索比特幣源碼的步伐,來分析一下C++11和boost庫在比特幣源碼中的應用。比特幣是一個純C++編寫的項目,用到了C++11和boost的許多特性,本文來總結一下相關特性的用法,或許未來的項目中可使用到。html

1 boost相關
1.1 boost::bind
    bind用於綁定參數到函數、函數指針、函數對象、成員函數上,返回一個函數對象。調用是須要引用<boost/bind.hpp>頭文件。linux

    如下是bind的幾個例子:ios

    (1) bind普通函數c++

    假設有以下的函數定義:編程

//normal function
static int fun(int x, int y, double z) {
x += 2;
cout << x << "," << y << "," << z << endl;
}

    綁定參數的時候能夠:設計模式

//綁定普通的函數
boost::bind(&fun, 1, 3, 5)();

    (2) 使用佔位符綁定api

    也可使用佔位符來綁定,參數的順序能夠任意:數組

//使用佔位符綁定
boost::bind(&fun, _3, _1, _2)(7, 8, 9);

    上面的例子等價於調用fun(9, 7, 8)app

    (3) bind仿函數ide

    bind也能夠綁定仿函數,假設以下一個簡單求和的仿函數:    

struct Sum {

int operator()(int a, int b) {
return a + b;
}
};

    能夠按以下方式綁定參數到仿函數上:    

//綁定仿函數
Sum sum;
cout << boost::bind<int>(sum, 7, 8)() << endl;

    (4) 綁定類的成員函數

    假設有以下類的定義:

class Base {

public:
virtual int f(int i);

};

int Base::f(int i) {
return i;
}

class Derived : public Base {

public:
virtual int f(int i);
};


int Derived::f(int i) {
return i + 2;
}

    bind一樣能夠綁定類的成員函數:

//綁定成員函數
Base base;
Derived d;
Base &refBase = d;
cout << boost::bind(&Base::f, base, 4)() << endl; //調用基類函數
cout << boost::bind(&Base::f, boost::ref(refBase), 4)() << endl; //boost::ref傳入指向子類的引用,調用子類函數

    注意,bind時若是經過boost::ref傳入引用,會觸發多態。例如上面例子中refBase是一個基類型的引用,可是實際指向一個派生類對象,bind時用boost::ref傳引用就會觸發多態。

    (5) 綁定函數指針

    bind也能夠綁定函數指針,仍是上面的兩個類,綁定成員函數指針能夠按下面的方式:

//綁定成員函數指針
typedef int (Base::*F)(int);
F fp = &Base::f;
cout << boost::bind(fp, boost::ref(refBase))() << endl;
    C++11標準庫中也有對應的實現:std::bind。

1.2 boost::thread_group
    boost::thread_group用於管理一組線程。能夠向線程組裏添加或者移除線程,向全部線程發送中斷信號,等待組內全部線程所有結束等等。

    這個類包含如下一些成員函數:

    thread_group::create_thread:建立一個新線程添加到thread_group;

    thread_group::add_thread:添加一個線程到線程組裏;

    thread_group::remove_thread:從線程組中移除一個線程;

    thread_group::join_all:等待組內全部線程執行完成;

    thread_group::interrupt_all:向組內全部線程發送終端信號;

    從支持的api能夠看出,thread_group只是統一的管理加入其中的線程,不要理解成線程池,由於線程池涉及到池中線程的複用和管理,邏輯更加複雜。

1.3 boost線程中斷
    boost線程默認打開中斷的,經過boost::thread的interrupt方法能夠中斷線程。值得注意的是線程只有在指定的中斷點才能被中斷,不然調用interrupt方法不會起做用。boost線程定義瞭如下幾個中斷點:

    //線程等待子線程結束    

    1. thread::join();    
    2. thread::timed_join();
    //線程在條件變量上等待

    3. condition_variable::wait();
    4. condition_variable::timed_wait();
    5. condition_variable_any::wait();
    6. condition_variable_any::timed_wait();

    //線程休眠

    7. thread::sleep();
    8. this_thread::sleep();

    //interruption_point()至關於一個標記點,表示當線程執行到這裏時能夠被中斷    

    9. this_thread::interruption_point()

    只有線程容許中斷時,thread::interrupt調用纔會在上面9箇中斷點上將線程中斷,線程被中斷時將會拋出boost::thread_interrupted異常。

    boost提供了api控制是否容許線程被中斷:

    boost::this_thread::disable_interruption:這是一個RAII對象,對象構造時關閉中斷,析構時恢復中斷;

    boost::this_thread::restore_interruption:也是一個RAII對象,構造時恢復線程中斷,析構時關閉中斷。restore_interruption對象構造時須要一個disable_interruption對象做爲參數,也就意味着一個線程只有用disable_interruption關閉中斷之後才能在以後再調用restore_interruption臨時恢復中斷。

    平時在開發過程當中咱們常常性會遇到須要一個線程在無限循環中處理,而後當某個條件觸發之後終端這個線程的訴求,通常常見的處理方式是定義一個bool型的變量,而後須要中斷的時候設置該bool變量。若是用boost,咱們能夠經過interrupt結合中斷點更加優雅的中斷線程。

    來看看比特幣中的使用示例。

    比特幣系統在初始化的時候會建立一個線程組,而後向線程組中添加若干個腳本校驗線程、任務調度線程以及從磁盤中加載區塊的線程:

//向線程組添加若干個腳本校驗線程
if (nScriptCheckThreads) {
for (int i=0; i<nScriptCheckThreads-1; i++)
threadGroup.create_thread(&ThreadScriptCheck);
}

// Start the lightweight task scheduler thread
//向線程組添加任務調度線程
CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler);
threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));

    這些線程一加入線程組後就像脫繮的野馬同樣狂奔起來,直到用戶發出stop控制指令之後,線程組的interrupt_all被調用,組內全部的線程就會收到中斷指令,在相關的中斷點上被中斷:

// After everything has been shut down, but before things get flushed, stop the
// CScheduler/checkqueue threadGroup
threadGroup.interrupt_all();
threadGroup.join_all();
    線程組中的線程在收到終端指令後會在各自的中斷點上被中斷,好比任務調度線程,在一個大的無限循環中包含了下面的代碼片斷,確保當收到中斷指令之後,線程能夠在特定的中斷點上被中斷:

while (!shouldStop() && taskQueue.empty()) {
// Wait until there is something to do.
// 中斷點,隊列爲空時收到中斷信號,直接中段線程
newTaskScheduled.wait(lock);
}

// Wait until either there is a new task, or until
// the time of the first item on the queue:

// wait_until needs boost 1.50 or later; older versions have timed_wait:
#if BOOST_VERSION < 105000
//中斷點,隊列不空,可是在等待調度通知時收到中斷信號,線程中斷,隊列中已有的任務不在執行
while (!shouldStop() && !taskQueue.empty() &&
newTaskScheduled.timed_wait(lock, toPosixTime(taskQueue.begin()->first))) {
// Keep waiting until timeout
}
#else
// Some boost versions have a conflicting overload of wait_until that returns void.
// Explicitly use a template here to avoid hitting that overload.
while (!shouldStop() && !taskQueue.empty()) {
boost::chrono::system_clock::time_point timeToWaitFor = taskQueue.begin()->first;
if (newTaskScheduled.wait_until<>(lock, timeToWaitFor) == boost::cv_status::timeout)
break; // Exit loop after timeout, it means we reached the time of the event
}
#endif
1.4 boost::signal2::signal
    也叫signal/slot(信號/插槽)機制,這是boost提供的一種用於模塊間解耦的機制。和觀察者相似。定義一些信號(事件),給信號註冊對應的處理器(任意能夠調用的實體,例如仿函數,函數指針等等),當信號觸發之後就會調用註冊的處理器處理之。boost提供的signal可讓咱們免於去本身動手擼個觀察者模式,寫各類不一樣場景的觀察者接口,開發者只關注兩件事:定義信號和給信號綁定不一樣的處理器。

    來看個比特幣中的例子:bitcoind在運行過程當中須要監視一些事件:好比有區塊從區塊鏈的主鏈上斷開了,有新的區塊連到了主鏈上,區塊鏈的定點有更新等等,利用boost::signal,能夠定義一組信號:

struct MainSignalsInstance {
boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip;
boost::signals2::signal<void (const CTransactionRef &)> TransactionAddedToMempool;
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef>&)> BlockConnected;
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected;
boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool;
boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed;
boost::signals2::signal<void (int64_t nBestBlockTime, CConnman* connman)> Broadcast;
boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked;
boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock;

// We are not allowed to assume the scheduler only runs in one thread,
// but must ensure all callbacks happen in-order, so we end up creating
// our own queue here :(
SingleThreadedSchedulerClient m_schedulerClient;

explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {}
};

    而後給這些信號綁定處理器:

void RegisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.m_internals->UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));
g_signals.m_internals->TransactionAddedToMempool.connect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1));
g_signals.m_internals->BlockConnected.connect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3));
g_signals.m_internals->BlockDisconnected.connect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1));
g_signals.m_internals->TransactionRemovedFromMempool.connect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1));
g_signals.m_internals->ChainStateFlushed.connect(boost::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, _1));
g_signals.m_internals->Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2));
g_signals.m_internals->BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
g_signals.m_internals->NewPoWValidBlock.connect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2));
}

    綁定信號處理器很是簡單,只須要connect一下就能夠了,綁定之後的signal就是一個可調用的實體了,當有信號觸發時,信號綁定的處理器就會被調用:

void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) {
m_internals->BlockChecked(block, state);
}

    來總結一下boost的信號/插槽機制:

    (1) 定義一組信號,信號是boost::signals2::signal,這是一個模板類,模板類的參數是該信號的處理器的簽名;

    (2) 經過signal::connect給信號綁定處理器,處理器的簽名必須和信號模板參數指定的一致;

    (3) signal也是一個可調用的實體,當有事件發生時,調用signal,則與之綁定的處理器就會被調用。    

1.5 boost visitor模式
    visitor模式主要的適用場景是集合中存在不一樣的元素,而對於不一樣的元素,有不一樣的操做,這種狀況下用visitor模式就比較合適。咱們舉個比特幣中的例子,比特幣中在對交易進行簽名的時候,須要根據提供的不一樣的類型來生成腳本地址,好比目前比特幣支持的就有CKeyID,CScriptID,CWithnessV0ScriptHash,CWithnessV0KeyHash,不少時候咱們可能會習慣性的擼出下面這樣的代碼:

if (destination typeof CKeyID) {
// CKeyID
} else if (destination typeof CScriptID) {
// CScriptID
} else if (destination typeof CWithnessV0KeyHash) {
// CWithnessV0KeyHash
} else if (destination typeof CWithnessV0ScriptHash) {
// CWithnessV0ScriptHash
}
    這種作法的最大問題在於若是項目代碼中分散着不少這種if-else構成的分支,若是未來新增一種類型,就須要逐個找出這些地方而後新增一個判斷分支,這樣很不利於後續的維護。這裏變化的是不一樣類型的對象接收到訪問請求時的處理方式,能夠用visitor將這一部分代碼收歸起來,看看比特幣的源碼中是如何處理的:

    首先提供一個visitor:

class CScriptVisitor : public boost::static_visitor<bool>
{
private:
CScript *script;
public:
explicit CScriptVisitor(CScript *scriptin) { script = scriptin; }

bool operator()(const CNoDestination &dest) const {
script->clear();
return false;
}

bool operator()(const CKeyID &keyID) const {
script->clear();
*script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
return true;
}

bool operator()(const CScriptID &scriptID) const {
script->clear();
*script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL;
return true;
}

bool operator()(const WitnessV0KeyHash& id) const
{
script->clear();
*script << OP_0 << ToByteVector(id);
return true;
}

bool operator()(const WitnessV0ScriptHash& id) const
{
script->clear();
*script << OP_0 << ToByteVector(id);
return true;
}

bool operator()(const WitnessUnknown& id) const
{
script->clear();
*script << CScript::EncodeOP_N(id.version) << std::vector<unsigned char>(id.program, id.program + id.length);
return true;
}
};
} // namespace

    而後將不一樣的類型用boost::variant來表示:

typedef boost::variant<CNoDestination, CKeyID, CScriptID, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination;

    boost::variant能夠理解爲聯合體,與聯合體不一樣的是它裏面能夠塞任何類型的數據。

    而後只要對這個variant運用visitor就能夠了:   

boost::apply_visitor(CScriptVisitor(&script), dest);
    這樣無論variant當前的值是什麼類型,visitor都能處理。若是未來想有不一樣的處理方式,換個visitor就能夠了。代碼中分散在四處難以維護的if-else分支如今被收歸到visitor中,即使有新的類型加進來,也只須要在visitor中改一處就好。

    上面的例子中其實已經看到了boost提供的visitor模式的用法,這裏總結一下:

    (1) 首先boost提供的visitor模式是須要和boost::variant來配合使用的。boost::variant裏是一些不一樣的類型,這些類型能夠經過給variant應用visitor來訪問。

    (2) 編寫visitor的實現,visitor須要繼承boost::static_visitor,這是一個模板類,模板參數表明操做的返回值;

    (3) boost::apply_visitor將visitor應用到variant上;

    與通常的設計模式的書中visitor模式的實現有所不一樣,boost的visitor提供的是基於模板的實現,用起來更簡便,效率也更高。

2 C++11
    比特幣源碼用到了許多C++11的特性,本節簡單來看看。

2.1 std::bind
    這個和前面介紹的boost::bind用法基本上一致,請參考boost::bind用法,這裏不在重複。

2.2 std::function
    std::function是對一切能夠調用的實體的封裝,好比函數指針,仿函數,lambda表達式等等,均可以轉化成function對象。std::function的最大的一個用處是實現延遲調用:能夠將回調先保存到function中,等到須要的時候在調用。比特幣中有大量的std::function的使用,基本上都是用於回調。如下的示例程序展現了std::function的使用:

#include <iostream>
#include <functional>

using namespace std;

int func(int i, int j) {
return i + j;
}

class Test {

public:

int f(int i, int j) {
return i + j;
}

static int static_func(int i, int j) {
return i + j;
}
};


struct FuncObj {
int operator()(int i, int j) {
return i + j;
}
};

int main(int argc, char **argv) {

std::function<int(int,int)> f;

//normal function
f = func;
cout << f(1, 2) << endl;

//function object
FuncObj fobj;
f = fobj;
cout << f(3, 4) << endl;

//member function
Test test;
f = std::bind(&Test::f, &test, std::placeholders::_1, std::placeholders::_2);
cout << f(5, 6) << endl;

//static member function
f = &Test::static_func;
cout << f(7, 8) << endl;
}
    示例程序中展現了用std::function來存儲普通函數,仿函數和成員函數(靜態與非靜態)的例子。另外C++11中還支持lamda表達式,稍後說明lamda表達式時來看看std::function和lamda表達式結合在一塊兒的例子。

2.3 std::thread
    在linux系統上,咱們不少時候可能會直接調用系統的pthread_create接口來建立線程,這種方式的限制在於不能直接綁定類的非靜態成員函數做爲線程體,若是想讓線程和類的非靜態成員函數綁定起來還須要額外進行封裝。而C++11引入了std::thread之後,事情就容易多了。如下是std::thread的使用示例:    

#include <iostream>
#include <functional>
#include <thread>
using namespace std;

int func(int i, int j) {
cout << "thread normal" << endl;
return i + j;
}

class Test {

public:

int f(int i, int j) {
cout << "thread member" << endl;
return i + j;
}
};


struct FuncObj {
int operator()(int i, int j) {
cout << "thread function object" << endl;
return i + j;
}
};

int main(int argc, char **argv) {

std::function<int(int,int)> f;

//普通函數
std::thread t1; //null thread
cout << "thread joinable?" << t1.joinable() << endl; //joinable=false
t1 = std::thread(func, 1, 2);
cout << "thread joinable?" << t1.joinable() << endl; //joinable=true

//仿函數
FuncObj fobj;
f = fobj;
std::thread t2(f, 3, 4);

//成員函數
Test test;
f = std::bind(&Test::f, &test, std::placeholders::_1, std::placeholders::_2);
{
std::thread t3(std::bind(&Test::f, &test, 20, 30));
t3.join(); //必須調用join或者detach,不然會拋出異常
}
//nromal
t1.join();
t2.join();
//t3.join();
}
    能夠看到,std::thread和std::function一塊兒,任何能夠調用的實體均可以被線程執行。關於std::thread有如下幾點須要注意:

    (1) std::thread對象和線程關聯之後,在對象銷燬前要麼經過join等待線程結束,要麼調用detach將線程和std::thread對象脫離,不然會拋出異常。

    (2) thread對象detach之後,線程依然會被OS調度運行;

    若是你還在用pthread_create等系統接口建立線程,請遠離他們,儘快擁抱std::thread吧。

2.4 lamda表達式
    c++11支持lamda表達式,用lamda表達式能夠實現匿名函數,尤爲是若是你的代碼中存在許多隻調用一次的小函數,用lamda表達式來重構掉他們是個不錯的主意。

    如下是C++11支持的四種lamda表達式的形式,死記硬背記住就能夠了:

    (1) [capture] (params) mutable exception attribute -> ret {body}

    這是最完整的lamda表達式,各個部分的解釋以下:

    (a) capture:表示在lamda表達式中全部可見的外部變量列表,至於這些變量是傳值仍是傳引用,請繼續看:

    [a, &b]:表示變量a以值的方式傳遞,變量b以引用的方式傳遞;

    []:空的表示不捕獲任何外部變量;

    [&]:表示以引用的方式捕獲可見域內全部的外部變量;

    [=]:表示以值的方式捕獲可見域內全部的外部變量;

    (b) mutable:表示在lamba表達式內可以改動被捕獲的變量,也可以訪問被捕獲對象的非const成員;

    (c) exception:lamba表達式內可能拋出的異常

    (2) [capture] (params) ->ret {body}

    const類型的lamda表達式,在表達式內部不能修改capture列表中捕獲的變量;

    (3) [capture] (params) {body} 

    省略了返回類型的lamba表達式,返回類型能夠根據表達式內部的return語句推斷出來,若是沒有return語句則認爲返回void;

    (4) [capture] {body}

    省略了參數列表,至關於一個無參數的匿名函數;

    如下代碼片斷示例了c++11中lamda表達式的使用:

#include <iostream>
#include <functional>
#include <thread>
using namespace std;


int main(int argc, char **argv) {

int a = 1;

//a以值的方式傳遞,lamda表達式爲const,沒法修改傳入變量
auto f = [a](){
cout << "a = " << a << endl;
//a = 2; //編譯錯誤
};

f();

//a以值的方式被捕獲,lamda表達式能夠改變傳入值
auto f2 = [a]() mutable {
a = 2;
cout << "a = " << a << endl;
};
f2();
cout << "after lamda:a = " << a << endl; //a = 1 由於以值的方式被捕獲,因此lamda執行之後a的值並不會改變

//a以引用的方式被捕獲
auto f3 = [&a]() mutable {
a = 2;
cout << "a = " << a << endl;
};
f3();
cout << "after lamda:a = " << a << endl; //a = 2 以引用的方式被捕獲,lamda執行之後a的值夜被改變了

//帶返回值的lamda表達式
int i = 2, j = 3;
auto f4 = [](int i, int j) ->int {
return i + j;
};
cout << f4(i, j) << endl;

//自動推斷返回值類型
auto f5 = [](int i, double d) {
return i + d;
};
cout << f5(1, 2.2f) << endl;
}
    比特幣的代碼中也有使用到lamda表達式,看看下面這段代碼:

std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override
{
return MakeHandler(
::uiInterface.NotifyHeaderTip.connect([fn](bool initial_download, const CBlockIndex* block) {
fn(initial_download, block->nHeight, block->GetBlockTime(),
GuessVerificationProgress(Params().TxData(), block));
}));
}
    這段代碼爲信號NotifyHeaderTip綁定處理器,其中處理器就用了lamda表達式。

    若是您此前尚未在C++中用過lamda表達式,初看到這種代碼的時候可能會不太理解,如今瞭解了lamda表達式,之後在碰見應該就會很親切了。

2.5 std::array
     C++11中引入的數組類型,與vector相比,std::array的內存位於堆棧中,因此性能更高。

     std::array的用法也很簡單,只要指定類型和大小就能夠了,如下是比特幣源碼中的一個例子:

static const std::array<unsigned char, 32> buff = {
{
17, 79, 8, 99, 150, 189, 208, 162, 22, 23, 203, 163, 36, 58, 147,
227, 139, 2, 215, 100, 91, 38, 11, 141, 253, 40, 117, 21, 16, 90,
200, 24
}
};
    須要注意的是std::array的對象不能由編譯器隱式轉換爲指針類型,所以對於下面的接口:

std::string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend)
    不能直接傳入std::array對象,而是須要經過其接口std::array::data()來獲取數組首元素的指針:

EncodeBase58(buff.data(), buff.data() + buff.size());
2.6 using的新用法
    C++11中using有了新用法,其中之一就是代替typedef:

    代替typedef:在C++11以前,咱們定義新類型的別名都是經過typedef來,在C++11中,typedef能夠經過using來代替,如下是來自比特幣中的一段代碼:    

using NodesStats = std::vector<std::tuple<CNodeStats, bool, CNodeStateStats>>;
virtual bool getNodesStats(NodesStats& stats) = 0;
    經過using定義了新類型NodesStats,實質上一個一個vector<std::tuple<>>類型。用using比typedef更簡單一些。

2.7 auto和decltype
    auto可讓編譯器自動爲咱們推斷變量的類型,這個特性能極大的簡化咱們的編程,提升效率。好比說若是不用auto,咱們只能寫出下面這樣的代碼:

std::map<int, vector<int> > amap;
std::map<int, vector<int> >::iterator iter = amap.begin();
    咱們得在鍵盤上敲入長長的一串來指定iter的類型,有了auto之後就方便多啦:    

std::map<int, vector<int> > amap;
auto iter = amap.begin();
    有了auto,你無須知道函數返回了一個什麼,無須知道某個類的成員是什麼類型,一切由編譯器自動推斷,還在等什麼,用起來再說吧。

    固然auto也不是萬能的,好比若是想定義函數的返回值爲auto就會編譯出錯:

static auto func(int i, int j) {
cout << "I am a function" << endl;
return i + j;
}
    此時須要用C++11中的trailing return type來進行:

static auto func(int i, int j) ->int{
cout << "I am a function" << endl;
return i + j;
}
    固然上面這個例子可能並非很恰當,由於咱們徹底知道函數會返回什麼,可是當涉及到模板的時候,問題可能會開始變的複雜,來看看比特幣中一個現實的案例:

template <typename T>
class reverse_range
{
T &m_x;

public:
explicit reverse_range(T &x) : m_x(x) {}

auto begin() const -> decltype(this->m_x.rbegin())
{
return m_x.rbegin();
}

auto end() const -> decltype(this->m_x.rend())
{
return m_x.rend();
}
};
    看看這個模板類的begin函數,由於沒法知道m_x的具體類型,也不知道m_x的rbegin的返回類型,所以begin()函數的返回類型是不能肯定的,此時trailing return type就發揮了做用。C++11新引入了decltype來計算表達式的返回類型(不會計算表達式,所以不用擔憂效率問題),用decltype讓編譯器去推算出返回類型。

3 小結    比特幣是一個純C++項目,其中用到了boost和C++11中的許多特性,本文簡單列舉一些,並非所有,讀者在閱讀源碼的過程當中能夠留意並學習,而後在項目中去嘗試使用這些新特性。--------------------- 做者:jupiterwangq 來源:CSDN 原文:https://blog.csdn.net/ztemt_sw2/article/details/81187098 版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索