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

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

1 boost相關

1.1 boost::bind

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

    以下是bind的幾個例子:

    (1) bind普通函數

    假設有如下的函數定義:

//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) 使用佔位符綁定

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

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

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

    (3) bind仿函數

    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中的許多特性,本文簡單列舉一些,並不是全部,讀者在閱讀源碼的過程中可以留意並學習,然後在項目中去嘗試使用這些新特性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章