o list_inserter 类
这个类负责向容器中插入元素。而且,它还是扩展库以支持自定义容器的关键所在。纲要
namespace boost { namespace assign { template< Function, Argument = void > class list_inserter { Function fun; public: explicit list_inserter( Function fun ); // conversion constructor template< class Function2, class Arg > list_inserter( const list_inserter<Function2,Arg>& ); public: template< class U > list_inserter& operator,( U u ); template< class U > list_inserter& operator=( U u ); // calls 'fun()' with default-constructed object list_inserter& operator()(); template< class U > list_inserter& operator()( U u ); template< class U, class U2 > list_inserter& operator()( U u, U2 u2 ) { // // if 'Argument' is 'void' // fun( u, u2 ); // else // fun( Argument( u, u2 ) ); // return *this; } // // similarly up to 5 arguments // }; template< class C > list_inserter< ... > push_back( C& ); template< class C > list_inserter< ... > push_front( C& ); template< class C > list_inserter< ... > insert( C& ); template< class C > list_inserter< ... > push( C& ); } // namespace 'assign' } // namespace 'boost'
请注意operator,() and operator()() 的参数是如何以不同的方式传递给fun()的,fun()依赖于Argument()的类型。因此如果我们仅仅传递一个模板参数给list_inserter,我们可以传送函数的"任意"参数列表。如果我们传递两个模板参数,我们可以用"任意"构造器构造类型。
因为返回的是一个指向list_inserter的引用,所以我们连接参数列表的方式空间效率很高。
o make_list_inserter()
这是一个简单的list_inserter的"构造"函数。这个函数一个典型的应用是通过boost::bind()的返回值调用(通常是一些无法看懂的、怪异的类模板)。
纲要
namespace boost { namespace assign { template< class Function > list_inserter<Function> make_list_inserter( Function fun ) { return list_inserter<Function>( fun ); } } }
o 定制参数列表的长度
这个库用boost.Preprocessor执行重载的operator()() and list_of()。缺省情况下,你可以使用5个参数,但是你可以通过在库的头文件中定义宏来改变参数的个数:
#define BOOST_ASSIGN_MAX_PARAMS 10 #include <boost/assign.hpp>
4、异常和异常安全
对异常的保证和原有函数相同。 对于标准容器,这意味着对单向插入方式的强壮保证和对多种插入方式的基本保证(也提供给被拷贝对象以基本保证)。
这些函数可以抛出类似于std::bad_alloc的标准异常。但不幸的是,标准并不保证在标准容器中内存分配失败可以被std::bad_alloc或是继承自std::exception的异常报告。
assignment_exception 类
这个异常被代理对象中转换操作符(the conversion operator)抛出,这个代理对象就是被list_of()返回的对象。
namespace boost { namespace assign { class assignment_exception : public std::exception { public: explicit assignment_exception( const char* what ); virtual const char* what() const throw(); }; } }
5、扩展库
让库支持新的容器是非常简单的,以下代码示范了如何在一个容器中使用operator+=():
template< class V, class A, class V2 > inline list_inserter< assign_detail::call_push_back< std::vector<V,A> >, V > operator+=( std::vector<V,A>& c, V2 v ) { return make_list_inserter( assign_detail::call_push_back< std::vector<V,A> >( c ) )( v ); }
call_push_back被定义在这里
template< class C > class call_push_back { C& c_; public: call_push_back( C& c ) : c_( c ) { } template< class T > void operator()( T r ) { c_.push_back( r ); } };
我们传递第二个模板参数给list_inserter,因此参数列表用来构造一个V对象。否则就以带有n个参数而不是一个的push_back()的调用来结束。 一个替代方式是结合使用boost::function 和 boost::bind。但是,这样的话,你必须记住在标准库里访问函数地址是非法的。
用一个以上的参数调用函数也是非常有用的。这个小例子演示了如何利用这个特性:
// // A class representing emails // class email { public: enum address_option { check_addr_book, dont_check_addr_book }; private: typedef std::map< std::string,address_option > address_map; // // Store list of persons that must be cc'ed // mutable address_map cc_list; // // This extra function-object will take care of the // insertion for us. It stores a reference to a // map and 'operator()()' does the work. // struct add_to_map { address_map& m; add_to_map( address_map& m ) : m(m) {} void operator()( const std::string& name, address_option ao ) { m[ name ] = ao; } }; public: // // This function constructs the appropriate 'list_inserter'. // Again we could have use 'boost::function', but it is // trivial to use a function object. // // Notice that we do not specify an extra template // parameter to 'list_inserter'; this means we forward // all parameters directly to the function without // calling any constructor. // list_inserter< add_to_map > add_cc( std::string name, address_option ao ) { // // Notice how we pass the arguments 'name' and 'ao' to // the 'list_inserter'. // return make_list_inserter( add_to_map( cc_list ) )( name, ao ); } }; // // Now we can use the class like this: // email e; e.add_cc( "Mr. Foo", email::dont_check_addr_book ) ( "Mr. Bar", email::check_addr_book ) ( "Mrs. FooBar", email::check_addr_book );
完全的例子请参见email_example.cpp。
6、样例
附带的例子可以在下列测试文件中找到:
- email_example.cpp my_vector_example.cpp
- multi_index_container.cpp
- array.cpp
- list_of.cpp
- std.cpp
- list_inserter.cpp
- list_of_work_around.cpp
7、支持的库
以下库中的容器经过测试可以被assign直接支持:- boost::array
- boost::multi_index_container
8、可移植性
assign库在以下编译器通过测试可以被成功编译:Microsoft VC++ 7.1、GCC 3.2(使用Cygwin)和Comeau 4.3.3
在不支持模板型别推导的平台,功能会受到限制。解决方案是对list_of()返回的对象明确调用成员函数:
{ using namespace std; using namespace boost; using namespace boost::assign; vector<int> v = list_of(1)(2)(3)(4).to_container( v ); set<int> s = list_of(1)(2)(3)(4).to_container( s ); map<int,int> m = map_list_of(1,2)(2,3).to_container( m ); stack<int> st = list_of(1)(2)(3)(4).to_adapter( st ); queue<int> q = list_of(1)(2)(3)(4).to_adapter( q ); array<int,4> a = list_of(1)(2)(3)(4).to_array( a ); }
注意你必须提供带参数的函数,以使右边的返回类型可以被推导。
在某些标准库中可能导致程序中断。问题之一是insert()可能没有工作:
map<int,int> next; insert( next )(1,2)(2,3); // compile-time error
解决方案是以map_list_of()代之:
map<int,int> next = map_list_of(1,2)(2,3);
9、历史 致谢
赋值与初始化库并不是一个新鲜玩意儿。库的功能非常类似于Leor Zolman的STL Container Initialization Library注, 不同的是它并不依赖于字符解析达到目标。
这个库是非侵入性的,对于所支持的容器,尽可能小的增加了前提条件。重载operator,()被认为是一个坏的尝试1。但是,在the Generative Matrix Computation Library 和 Blitz 中初始化矩阵(参见 2和3)是成功的先例。assign通过让独立函数返回一个负责初始化的对象,以一种安全的方式重载了operator,()。它采用显式的方式避免了程序员使用operator,().
最近有一些关于使C++支持更好的初始化方式的讨论。(参见4)。
特别感谢
- Leor Zolman 参与了许多有决定意义的讨论。
- Tom Brinkman for being review manager。
- Joaquín Muñoz 将库移植到 vc6/vc7 所做的工作。
- Pavel Vozenilek 提供了数不清的建议, 改进和可移植性的修正。
10、参考书目
1. Scott. Meyers, "More Effective C++", Item 7, Addison Wesley, 1996
2. K. Czarnecki and U.W. Eisenecker, "Generative programming", Addison-Wesley, 2000
3. http://www.oonumerics.org/blitz/
4. Gabriel Dos Reis and Bjarne Stroustrup, Generalized Initializer Lists", 2003
Copyright © Thorsten Ottosen 2003-2004
注1 Leor Zolman的STL Container Initialization Library通过字符解析达到分解参数的目的,但是只支持push_back与insert(关联容器)两种行为,所以slist、stack、deque都无法支持,且对值类型有些挑剔(eg. 不支持pair)。译者曾借助于型别推导改进使之支持到所有容器。它的好处在于简单的函数接口(make_cont,set_cont,app_cont,以及make_cont_p……),只有一个头文件,缺点在于需要借助于正则表达式,编译时还需要导入库文件,会麻烦一些。