動態 Property Maps動態 property map 接口提供了通過一個動態類型接口對一組 property maps 的訪問。算法可以用它來操作 property maps 而無需在編譯期知道它們的鍵類型和值類型。類型安全的代碼可以使用動態 property maps 來更容易和更完整地連接腳本語言和其它基於文本的鍵-值數據表示法。
Boost Property Map 庫規定了靜態的類型安全接口,它的鍵-值對可以被泛型算法操作。典型地,使用 property maps 的算法被按其使用的 property maps 類型進行參數化,並且使用 Boost Property Map 庫所規定的接口來操作它們。
以下泛型函數示範了 property map 的基本用法。
template <typename AgeMap, typename GPAMap>
void
manipulate_freds_info(AgeMap ages, GPAMap gpas) {
typedef typename boost::property_traits<AgeMap>::key_type name_type;
typedef typename boost::property_traits<AgeMap>::value_type age_type;
typedef typename boost::property_traits<GPAMap>::value_type gpa_type;
name_type fred = "Fred";
age_type old_age = get(ages, fred);
gpa_type old_gpa = get(gpas, fred);
std::cout << "Fred's old age: " << old_age << "\n"
<< "Fred's old gpa: " << old_gpa << "\n";
age_type new_age = 18;
gpa_type new_gpa = 3.9;
put(ages, fred, new_age);
put(gpas, fred, new_gpa);
}
這個函數根據兩個 property map 類型 AgeMap 和 GPAMap 來參數化,並接受這兩個類型各一個值參數。函數中使用了 property_traits 接口,在編譯期獲得這兩個 property map 的值類型鍵類型。煞後用 get 函數取出 Fred 的舊信息,再用 put 函數更新信息。要求可讀的 Property Map 概念要求具有 get 函數,而讀/寫 Property Map 概念則要求同時具有 get 和 put 函數。
上面這個函數不僅要求兩個類型參數要符合 property map 概念,還有其它的假設。AgeMap 和 GPAMap 必須具有相同的鍵類型,該類型必須可以從字符串構造。此外,AgeMap 的值類型必須可以從 int 構造。雖然這些要求並沒有顯式地聲明,但是它們都會在編譯時進行靜態的類型檢查,如果不能滿足就會產生編譯期的錯誤。
雖然 property map 接口的靜態類型通常可以提供所需要的編譯期安全,但是有些算法要求更為動態的 property map 接口。例如,Boost Graph 庫(BGL)提供了一些函數,它們可以通過對一個文本的圖形描述(如 GraphML 文件)進 行解釋,來初始化一個圖。這種通用的圖形描述語言可以指定任意數量的邊屬性和頂點屬性,以字符串來表示鍵-值對。圖的讀入函數應該要處理這些任意的屬性, 但是由於函數模板只能被固定數量的 property maps 參數化,所以處理 property maps 的傳統技術不能實現這一點。
動態 property maps 特別用於需要在運行期進行檢測的 property maps 接口。幾個不同的組件組合起來,提供了對動態 property map 的支持。dynamic_properties 類容納了一組異質的對象,它們符合來自於 Boost Property Map 庫的概念。當每一個 property map 被加入到容器中時,會被賦予一個字符串鍵,可以用這個鍵來定位它。在內部,dynamic_properties 將每一個包含的 property map 用動態 property map 接口來適配,該接口提供了 get 和 put 函數,這些函數可以用任意類型的值來調用,只有很少的要求。動態 property map 在內部將鍵-值對進行轉換以滿足底層的 property map 的要求,如果它做不到,就會拋出一個運行期異常。
將前面的例子用 dynamic_properties 接口重寫一下:
void manipulate_freds_info(boost::dynamic_properties& properties)
{
using boost::get;
std::string fred = "Fred";
int old_age = get<int>("age", properties, fred);
std::string old_gpa = get("gpa", properties, fred);
std::cout << "Fred's old age: " << old_age << "\n"
<< "Fred's old gpa: " << old_gpa << "\n";
std::string new_age = "18";
double new_gpa = 3.9;
put("age",properties,fred,new_age);
put("gpa",properties,fred,new_gpa);
}
新的函數並不是以 property map 類型進行參數化的模板,而是一個具體的函數,接受一個 dynamic_properties 對象。此外,其中的代碼也不再需要對鍵類型或值類型進行引用:鍵和值都以字符串來表示。雖然如此,但是在需要時,函數還是可以使用非字符串類型的。例如,Fred 的舊的年齡就表示為一個 int. 其值可能通過調用帶有類型參數的 get 函數來取出,該類型參數決定了它的返回類型。最後,get 和 put 函數都提供了一個字符串鍵來決定所需訪問的屬性。
以下是一個例子,示範了如何調用上述函數:
int main()
{
using boost::get;
// 用 associative_property_map 創建 property maps
std::map<std::string, int> name2age;
std::map<std::string, double> name2gpa;
boost::associative_property_map< std::map<std::string, int> >
age_map(name2age);
boost::associative_property_map< std::map<std::string, double> >
gpa_map(name2gpa);
std::string fred("Fred");
// 添加 鍵-值 信息
name2age.insert(make_pair(fred,17));
name2gpa.insert(make_pair(fred,2.7));
// 創建並組裝動態接口
boost::dynamic_properties properties;
properties.property("age",age_map);
properties.property("gpa",gpa_map);
manipulate_freds_info(properties);
std::cout << "Fred's age: " << get(age_map,fred) << "\n"
<< "Fred's gpa: " << get(gpa_map,fred) << "\n";
}
這段代碼首先用 std::map 和 associative_property_map 適配器創建兩個 property map. 然後用鍵-值數據來初始化 property maps, 再構造一個 dynamic_properties 對象並添加兩個 property maps, 分別以字符串 "age" 和 "gpa" 為鍵。最後 manipulate_freds_info 被傳入這個 dynamic_properties 對象並打印出修改的結果。
如上所示,dynamic_properties 對像在需要時提供了一個對 property maps 的動態類型接口,而在應用程序的其它地方依然保持了 property map 的靜態類型。
class dynamic_properties
dynamic_properties 類為一組 property map 提供了一個動態類型的接口。要使用它,你必須通過 property 成員函數用多個 property map 組裝出該類的一個對象。
dynamic_properties()
dynamic_properties(
const boost::function<
std::auto_ptr<dynamic_property_map> (
const std::string&, const boost::any&, const boost::any&)
>& fn)
dynamic_properties 對象可以用一個函數對像來構造,該函數對像被調用時應創建一個新的 property map. 當試圖 put 一個鍵-值對到一個不存在的 dynamic_properties 鍵時,將以 dynamic_properties 的鍵以及相關的屬性鍵和屬性值調用該函數。如果 dynamic_properties 是缺省構造的,則這樣的 put 嘗試將會拋出 property_not_found.
template<typename PropertyMap>
dynamic_properties&
property(const std::string& name, PropertyMap property_map)
該成員函數添加一個 property map 到所包含的 maps 組中,以 name 為鍵。
要求:PropertyMap 必須符合可讀 Property Map 或 讀/寫 Property Map.
void insert(const std::string& name, std::auto_ptr<dynamic_property_map> pm)
該成員函數直接添加一個 dynamic_property_map 到集合中,以 name 為鍵。
iterator begin()
const_iterator begin() const
該成員函數返回一個迭代器,指向 dynamic_properties 對像所持有的 property maps 組。
iterator end()
const_iterator end() const
該成員函數返回指向 dynamic_properties 對像所持有的 property maps 組的結束迭代器。它用於結束對動態 property maps 組的迭代操作。
iterator lower_bound(const std::string& name)
該成員函數返回一個迭代器,指向第一個 dynamic_properties 鍵為 name 的 property map. 記住,可以有多個 property maps 具有相同的 dynamic_properties 鍵,只要它們的 property map 鍵類型不同。
不變式:區間 [ lower_bound(name), end() ) 包含每一個 dynamic_properties 鍵為 name 的 property map.
std::auto_ptr<boost::dynamic_property_map>
ignore_other_properties(const std::string&,
const boost::any&,
const boost::any&)
如果被傳遞給 dynamic_properties 的構造函數,該函數可以使得 dynamic_properties 對像忽略將值置入未知鍵的嘗試而不會發出一個錯誤。
template<typename Key, typename Value>
bool put(const std::string& name, dynamic_properties& dp, const Key& key,
const Value& value)
該函數添加一個鍵-值對到名字和鍵類型相匹配的 property map 中。如果找不到匹配的 property map,則行為取決於 property map 生成器的有效性。如果在構造 dynamic_properties 對像時指定了一個生成器函數,則會調用生成器函數來創建一個新的 property map。如果生成器不能生成一個 property map (返回一個空的 auto_ptr),則 put 函數返回 false。如果另一方面,dynamic_properties 對像沒有 property map 生成器(即它是缺省構造的),則拋出 property_not_found。如果找到的 property map 不支持 put, 則拋出 dynamic_const_put_error。
template<typename Value, typename Key>
Value get(const std::string& name, const dynamic_properties& dp,
const Key& key)
該函數從名字和鍵類型相匹配的 property-map 中取出值。如果 Value 是 std::string, 則 property map 的值類型必須為 std::string 或者符合 OutputStreamable. 在後一種情況下,get 函數將值轉換為字符串。如果沒有找到匹配的 property map, 則拋出 dynamic_get_failure.
class dynamic_property_map
該類描述了一個接口,dynamic_properties 使用該接口與用戶的 property maps 進行多態的交互。
boost::any get(const any& key)
給定一個鍵的表示,返回與該鍵相關聯的值。
要求:1) 傳入的鍵對像必須可以轉換為該 property map 的鍵類型的一個值。轉換的細節未指定。2) 為了使該表達式有效,鍵必須與某個值相關聯,否則結果將是未定義的。
std::string get_string(const any& key)
給定一個鍵的表示,返回與該鍵相關聯的值的字符串表示。
要求:1) 傳入的鍵對像必須可以轉換為該 property map 的鍵類型的一個值。轉換的細節未指定。2) 為了使該表達式有效,鍵必須與某個值相關聯,否則結果將是未定義的。3) property map 的值類型必須符合 Output Streamable.
void put(const any& key, const any& value)
給定一個鍵的表示和一個值的表示,在 property map 中關聯該鍵與值。
要求:1) 傳入的鍵對像必須可以轉換為該 property map 的鍵類型的一個值。轉換的細節未指定。2) 傳入的值對像必須可以轉換為該 property map 的值類型的一個值。轉換的細節未指定。3) property map 不必支持該成員函數,這時將產生一個錯誤。這是可讀 Property Map 概念的運行期模擬。
const std::type_info& key() const
返回一個 type_info 對象,表示 property map 的鍵類型。
const std::type_info& value() const
返回一個 type_info 對象,表示 property map 的值類型。
struct dynamic_property_exception : public std::exception {
virtual ~dynamic_property_exception() throw() {}
};
struct property_not_found : public std::exception {
std::string property;
property_not_found(const std::string& property);
virtual ~property_not_found() throw();
const char* what() const throw();
};
struct dynamic_get_failure : public std::exception {
std::string property;
dynamic_get_failure(const std::string& property);
virtual ~dynamic_get_failure() throw();
const char* what() const throw();
};
struct dynamic_const_put_error : public std::exception {
virtual ~dynamic_const_put_error() throw();
const char* what() const throw();
};
在特定的情形下,調用 dynamic_properties 成員函數會拋出以上異常之一。當不需要太高的精確度時,三個具體異常都可以使用 dynamic_property_exception 名字來捕獲。另外,以上所有異常均派生自標準的 std::exception,可進行更為通用的錯誤處理。產生這些異常的特定情形在前文中已有描述。