1. 程式人生 > >Boost.ASIO原始碼:service_registry::use_service()詳解以及相關type_traits解析

Boost.ASIO原始碼:service_registry::use_service()詳解以及相關type_traits解析

這都是神仙寫的程式碼吧

沒什麼,這個標題只是忍不住表達一下對ASIO的驚歎。
曾經看《STL原始碼剖析》對裡面的type_traits的設計驚為天人,沒想到看ASIO庫的時候又看到了同樣的設計模式,雖然對於C++功底還不深的我來說看起來十分的費勁,但我還是決定好好的自己理解一遍,並把它記錄下來。由於不喜歡黑盒法,所以幾乎每個函式我都會跳進去瞅瞅,所以雖然use_service邏輯不復雜,但這裡介紹依舊會很細。

use_service()的功能與內部邏輯

首先,解釋一下,use_service存在於execution_context.hpp中,是一個全域性模板函式,用於得到一個指定type的Service物件。以下為函式宣告:

template <typename Service> Service& use_service(execution_context&);
template <typename Service> Service& use_service(io_context&);

Service是ASIO中的一種概念,如scheduler和epoll_reactor都是Service,其實Service具體是啥我也沒有理解得特別深 ,但它們都有一個明顯的共同點,就是都繼承自execution_context::service

下面回到use_service函式上來,這裡以一個use_service的使用情況來開始說明。

// scheduler類的一個成員函式
void scheduler::init_task()
{
  mutex::scoped_lock lock(mutex_);
  if (!shutdown_ && !task_)
  {

    task_ = &use_service<reactor>(this->context());  //關注這一行就行了
    /* typedef class epoll_reactor reactor; */  // 這是在一個遙遠的山卡拉角落對於reactor的定義
    
    op_queue_.push(
&task_operation_); wake_one_thread_and_unlock(lock); } }

先說明作用,那一句能得到一個reactor例項並賦給task_,這個reactor就是epoll_reactor類,它也是個execution_context::service的子類。
進入這個use_service函式內部:

template <typename Service>
inline Service& use_service(execution_context& e)
{
  // Check that Service meets the necessary type requirements.
  (void)static_cast<execution_context::service*>(static_cast<Service*>(0));

  return e.service_registry_->template use_service<Service>();
}

對於中間那一行看起來執行不了任何功能的連續強轉剛開始我也是一頭霧水,後來才發現實際上這行就是保證傳進來的這個模板型別Service,是execution_context::service的子類,如果這個Service實際上是vector或者double之類的型別,那麼這一句就會報編譯錯誤。
嗯很巧妙的寫法,看起來效率很高。不過為什麼不用 template <typename Service extends execution_context::service>的寫法呢。希望有人能給我指正一下。
同時在這裡能看出來傳進來的這個execution_context物件是幹啥的了,在return 那一句可以看到它取出了這個execution_context物件的service_registry_,並轉而呼叫這貨的use_service。下面為了解釋service_registry這個成員,截出了execution_context類中的兩個個片段:

private:
  // The service registry.
  boost::asio::detail::service_registry* service_registry_;
execution_context::execution_context()
  : service_registry_(new boost::asio::detail::service_registry(*this))
{
}

execution_context::~execution_context()
{
  shutdown();
  destroy();
  delete service_registry_;
}

每一個execution_context物件都與一個service_registry物件互相繫結,他們生一起生,死一起死。而這個service_registry類是用來管理所有的execution_context::service物件的,具體怎麼管理這裡不細講了,反正就是個管理器。
再進到這個service_registry的use_service方法中:

template <typename Service>
Service& service_registry::use_service(io_context& owner)
{
  execution_context::service::key key;   // 這個key對每種型別的Service是唯一的。
  init_key<Service>(key, 0);   // 根據Service型別初始化(也可以說得到)它的key。這句是關鍵!!
  factory_type factory = &service_registry::create<Service, io_context>;   // 這裡得到一個建立Service的方法
  return *static_cast<Service*>(do_use_service(key, factory, &owner));//具體建立還是不建立再說,反正這句能得到一個目標Service物件
}

這裡先不探究key長啥樣,反正知道它對每種型別的Service是唯一的就行了。init_key是關鍵,它能根據Service的型別資訊得到一個唯一的key(令人驚豔的是大部分判斷邏輯都是編譯時完成,也就是type_traits思想,太6了),後面解釋內部邏輯,內部涉及到了type_traits機制。方法體內第三行得到的是一個方法指標,這個方法可以用來建立一個Service物件:

  // The type of a factory function used for creating a service instance.
  	typedef execution_context::service*(* factory_type )(void*);  // 在另一個角落對factory_type的定義
	
	// ... 十萬光年的距離

	template <typename Service, typename Owner>
	execution_context::service* service_registry::create(void* owner)
	{
	  return new Service(*static_cast<Owner*>(owner));
	}

回到service_registry的use_service函式,裡面最後一行的do_use_service方法邏輯其實非常簡單,簡單到不想貼那一坨程式碼來增加我的篇幅。service_registry中維護了一個execution_context::service連結串列(其實每個execution_context::service都有個next指標),do_use_service就是到service_registry中找它裡面有沒有儲存這個型別的Service物件,有就拿出來,沒有就用傳進來的factory方法建立一下,並把這個owner也就是某個execution_context(或者說io_context)給綁到這個創建出來的Service上(execution_context::service的建構函式要傳入一個execution_context物件)。

init_key()詳解

先看程式碼:

	// Initalise a service's key when the key_type typedef is not available.
	template <typename Service>
	inline void service_registry::init_key(
	    execution_context::service::key& key, ...)
	{
	  init_key_from_id(key, Service::id);
	}

	// Initalise a service's key when the key_type typedef is available.
	template <typename Service>
	void service_registry::init_key(execution_context::service::key& key,
	    typename enable_if<
	      	is_base_of<typename Service::key_type, Service>::value>::type*)
	{
	  key.type_info_ = &typeid(typeid_wrapper<Service>);
	  key.id_ = 0;
	}

	// Initalise a service's key when the key_type typedef is available.
	template <typename Service>
	void service_registry::init_key_from_id(execution_context::service::key& key,
	    const service_id<Service>& /*id*/)
	{
	  key.type_info_ = &typeid(typeid_wrapper<Service>);
	  key.id_ = 0;
	}

這裡init_key方法有2個過載,如果Service有定義Service::key_type這個欄位,就走那個引數一大坨的過載,反之,就走那個引數列表比較短的過載。如果沒有定義Service::key_type,就會根據Service::id來初始化Service的key。
接下來稍微解釋一下這個key。execution_context::service::key就那兩個屬性:type_info_和id_。type_info_比較好理解,就是STL裡面那個type_info,描述型別資訊的,type_wrapper是一個模板空類,(我猜)應該是為了簡化typeid返回的type_info中資料。另一個id_就是一個execution_context的內部類id的物件,這個execution_context::id類是空的,也是不可複製的(繼承自noncopyable),至於用法現在還沒看懂。。。為了方便理解再貼上key的匹配方法:

	bool service_registry::keys_match(
	    const execution_context::service::key& key1,
	    const execution_context::service::key& key2)
	{
	  if (key1.id_ && key2.id_)
	    if (key1.id_ == key2.id_)
	      return true;
	  if (key1.type_info_ && key2.type_info_)
	    if (*key1.type_info_ == *key2.type_info_)
	      return true;
	  return false;
	}

可以看出優先匹配id_,再匹配type_info_。

enable_if和is_base_of中的細節

這是我最感興趣的一部分,雖然與use_service的主要邏輯關係並不大,但還是想仔細研究下。
先把關鍵程式碼再貼上來一次:

template <typename Service>
void service_registry::init_key(execution_context::service::key& key,

	// 就是這裡
    typename enable_if<
      is_base_of<typename Service::key_type, Service>::value>::type*)
      
{
  key.type_info_ = &typeid(typeid_wrapper<Service>);
  key.id_ = 0;
}

先解釋enable_if,先上程式碼:

  template<bool, typename _Tp = void>
    struct enable_if 
    { };

  template<typename _Tp>
    struct enable_if<true, _Tp>
    { typedef _Tp type; };

可以看到下面那個是一個偏特化版本,其中也只有偏特化版本才有type的定義。可以看到enable_if的呼叫都是typename enable_if <[一坨東西]>,如果這傳過來的[一坨東西]是個bool值,那麼例項化上面那個版本,如果傳進來的是bool以外的什麼鬼東西,就走下面那個版本。
這裡涉及到一個概念SFINAE:Substitution Failure Is Not An Error(匹配失敗不是錯誤),翻譯成普通話就是在進行模板特化的時候,會去選擇那個正確的模板,避免失敗。以我們的例子來說明,前面說過init_key有兩個版本,當我們呼叫init_key(key, 0)時,按道理會匹配enable_if那個版本的init_key過載,但是編譯器在根據我們傳進來的引數例項化enable_if模板時發現例項化出來的類沒有type定義(例項化出來的是上面那個enable_if版本),但我們卻倔強地用了enable_if<[一坨東西]>::type*,照理說這裡是要報錯的,但按照SFINAE原則,這不算錯,於是編譯器放棄這個函式過載版本,用另一個init_key的過載版本,於是程式得以正常執行。
接下來看is_base_of,以及它的基類integral_constant:

	template<typename _Base, typename _Derived>
	    struct is_base_of
	    : public integral_constant<bool, __is_base_of(_Base, _Derived)>
	    { };
template<typename _Tp, _Tp __v>
    struct integral_constant
    {
      static constexpr _Tp                  value = __v;
      typedef _Tp                           value_type;
      typedef integral_constant<_Tp, __v>   type;
      constexpr operator value_type() const { return value; }

      constexpr value_type operator()() const { return value; }
    };

  /// The type used as a compile-time boolean with true value.
  typedef integral_constant<bool, true>     true_type;

  /// The type used as a compile-time boolean with false value.
  typedef integral_constant<bool, false>    false_type;

這裡引用一下網上對integral_constant的解釋:用integral_constant方便地定義編譯期常量,而無需再通過enum和static const變數方式。
is_base_of實際上只用到了下面的2個偏特化版本的integral_constant:true_type和false_type,根據__is_base_of返回的值是true還是false決定is_base_of<Base, Derived>::value返回的true還是false,從而影響enable_if的例項化。
至於最關鍵的__is_base_of()函式我沒找到原始碼。。。網上也找不到資料。。。不過根據我的第六感,這應該是返回_Derived是不是_Base的基類的判斷。若有對該函式知情者望告知多謝。。。