Copyright © 2003-2004 Thorsten Ottosen
译者:nirvana
对Assign的使用,修改和授权须依据Boost软件许可协议 v1.0(参见http://www.boost.org/LICENSE_1_0.txt)
Bjarne Stroustrup——《The Design and Evolution of C++》
1、简介
这个库的设计目的是为了使容器可以更方便地填充数据,这主要借助重载通过重载operator,()和operator()()实现。对两个操作符重载使得构造值列表然后把它们拷贝到相应容器里成为可能:
- 逗号分隔的列表:
vector<int> v;
v += 1,2,3,4,5,6,7,8,9;
- 括号分隔的列表:
map<string,int> m; insert( m )( "Bar", 1 )( "Foo", 2 );
这些列表在研究、测试和原型化场合非常有用(These lists are particularly useful in learning, testing, and prototyping situations),在其他方面也相当的便利。这个库为标准库的容器提供了预定义的操作符,但是大多数函数可以在符合标准的容器下工作。用户也可以为库扩展自定义类型,这样一个成员函数就可以用一个值列表调用而不是常规的参数。
2、教程
用不了两分钟,你就应该能够使用这个库。库的主要组件将用一下章节阐明:
- operator+=()
- operator()()
- list_of()
- map_list_of()
- repeat() and repeat_fun()
- 一个"复杂" 的例子
前两个函数用来在一个已经建立的容器对象后添加元素,而接下来的两个则是在使用时才初始化一个容器对象。
o operator+=()
operator+=()用来填充一个vector(或是任何其他标准容器),就象这样
#include <boost/assign/std/vector.hpp> // for "operator+=()" #include <boost/assert.hpp>; using namespace std; using namespace boost::assign; // bring "operator+=()" into scope { vector<int> values; values += 1,2,3,4,5,6,7,8,9; // insert values at the end of the container BOOST_ASSERT( values.size() == 9 ); BOOST_ASSERT( values[0] == 1 ); BOOST_ASSERT( values[9] == 9 ); }
这里我们仅仅是用一些数字填充容器,但是这个列表可以由任意表达式组成,只要每个表达式的结果能够被转换为容器的值类型。
o operator()()
operator()()并不被直接调用, 而是代之以一个函数,这个函数返回一个定义了operator()()的代理对象。并且这个函数总是与将值列表拷贝到容器的成员函数同名。因此,要用一些值对填充map,你该这样做:
#include <boost/assign/list_inserter.hpp> // for "insert()" #include <boost/assert.hpp> #include <string> using namespace std; using namespace boost::assign; // bring "insert()" into scope { map<string,int> months; insert( months ) ( "january", 31 )( "february", 28 ) ( "march", 31 )( "april", 30 ) ( "may", 31 )( "june", 30 ) ( "july", 31 )( "august", 31 ) ( "september", 30 )( "october", 31 ) ( "november", 30 )( "december", 31 ); BOOST_ASSERT( m.size() == 12 ); BOOST_ASSERT( m["january"] == 31 ); }
注意,当我们需要用一些参数(默认是5个,你也可以自行定义)构造容器对象时,operator()() 是非常方便的。对于序列,这一点同样适用:
#include <boost/assign/list_inserter.hpp> // for "push_front()" #include <boost/assert.hpp> #include <string> #include <utility> using namespace std; using namespace boost::assign; // bring "push_front()" into scope { typedef pair< string,string > str_pair; deque<str_pair> deq; push_front( deq )( "foo", "bar")( "boo", "far" ); BOOST_ASSERT( deq.size() == 2 ); BOOST_ASSERT( deq.front().first == "boo" ); BOOST_ASSERT( deq.back().second == "bar" ); }
除了push_front(),如果容器有与之对应的成员函数的话,还可以使用push_back()。空的括号用于插入默认构造函数,例如, push_front( deq )()() 将插入两个缺省的std::pair对象。
如果嫌operator()()太麻烦,那么也可以选择push_front()
deque<int> di;
push_front( di ) = 1,2,3,4,5,6,7,8,9;
BOOST_ASSERT( di.size() == 9 );
BOOST_ASSERT( di[8] == 9 );
为了使之更纯粹, 以上的代码都没有限制在标准容器中,而是可以工作在所有符合标准,并且拥有正确的成员函数的容器。只有operator+=()被严格限制在标准容器中使用。
o list_of()
但是如果要用值列表初始化一个容器怎么办?是该list_of()登场了。用list_of() 我们可以建立一个匿名的列表,这个列表可以被自动转换为任何容器:
#include <boost/assign/list_of.hpp> // for "list_of()" #include <boost/assert.hpp> #include <list> #include <stack> #include <string> using namespace std; using namespace boost::assign; // bring "list_of()" into scope { const list<int> primes = list_of(1)(2)(3)(5)(7)(11); BOOST_ASSERT( primes.size() == 6 ); BOOST_ASSERT( primes.back() == 11 ); BOOST_ASSERT( primes.front() == 1 ); const stack<string> names = list_of( "Mr. Foo" )( "Mr. Bar")( "Mrs. FooBar" ).to_adapter(); const stack<string> names2 = (list_of( "Mr. Foo" ), "Mr. Bar", "Mrs. FooBar" ).to_adapter(); BOOST_ASSERT( names.size() == 3 ); BOOST_ASSERT( names[0] == "Mr. Foo" ); BOOST_ASSERT( names[2] == "Mrs. FooBar" ); }
如果要初始化容器适配器, 我们就要通过调用to_adapter()给编译器一点帮助。第二个例子也告诉我们,可以使用带有list_of()的逗号分隔列表,但是你需要给整个右值加上圆括号。值得注意的是,list_of()的第一个参数决定了匿名列表的类型。假设在一个stack中,匿名列表由const char*字符串构成,然后他被转换为值类型为string的stack。只要在已存储的类型中转换是可能的,那么这个转换就总是可能的。
请注意list_of()生成的列表甚至可以被转换为boost::array<T,sz>,参见Supported Libraries。
o map_list_of()
该函数完全是为了更方便地在map下工作而定义的。用法很简单:
#include <boost/assign/list_of.hpp> // for "map_list_of()" #include <boost/assert.hpp> #include <map> using namespace std; using namespace boost::assign; // bring "map_list_of()" into scope { map<int,int> next = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6); BOOST_ASSERT( next.size() == 5 ); BOOST_ASSERT( next[ 1 ] == 2 ); BOOST_ASSERT( next[ 5 ] == 6 ); // or we can use "list_of()" by specifying what type // the list consists of next = list_of< pair<int,int> >(6,7)(7,8)(8,9); BOOST_ASSERT( next.size() == 3 ); BOOST_ASSERT( next[ 6 ] == 7 ); BOOST_ASSERT( next[ 8 ] == 9 ); }
o repeat() and repeat_fun()
有时候多次重复相同的值让人愤怒。这种时候用repeat()会更方便:
#include <boost/assign/list_of.hpp> #include <boost/assign/std/vector.hpp> #include <boost/assert.hpp> using namespace std; using namespace boost::assign; { vector<int> v; v += 1,2,3,repeat(10,4),5,6,7,8,9; // v = [1,2,3,4,4,4,4,4,4,4,4,4,4,5,6,7,8,9] BOOST_ASSERT( v.size() == 3 + 10 + 5 ); v = list_of(1).repeat(5,2)(3); // v = [1,2,2,2,2,2,3] BOOST_ASSERT( v.size() == 1 + 5 + 1 ); push_back( v )(1).repeat(1,2)(3); // v = old v + [1,2,3] BOOST_ASSERT( v.size() == 10 ); }
如上所见,repeat()的第一个参数是值,第二个参数是重复的次数。
repeat_fun()能够构造一个更加通用的列表:
#include <boost/assign/std/vector.hpp> #include <boost/assert.hpp> #include <cstdlib> // for "rand()" using namespace std; using namespace boost::assign; template< class T > struct next { T seed; next( T seed ) : seed(seed) { } T operator()() const { return seed++; } }; { vector<int> v; v += 1,2,repeat_fun(4,&rand),4; // v = [1,2,?,?,?,?,4] BOOST_ASSERT( v.size() == 7 ); push_back( v ).repeat_fun(4,next<int>(0))(4).repeat_fun(4,next<int>(5)); // v = old v + [0,1,2,3,4,5,6,7,8] BOOST_ASSERT( v.size() == 16 ); }
对于repeat_fun()来说,第二个参数仅仅需要满足是个无参的函数。
o 一个"复杂"的例子
最后一个例子,假定我们需要记录足球比赛的结果,胜一场积一分,负一场积0分。如果每个小组三支队伍打单循环,那么代码看起来应该是这样:
#include <boost/assign/list_of.hpp> #include <boost/assign/list_inserter.hpp> #include <boost/assert.hpp> #include <string> #include <vector> using namespace std; using namespace boost::assign; { typedef vector<int> score_type; typedef map<string,score_type> team_score_map; typedef pair<string,score_type> score_pair; team_score_map group1, group2; // // method 1: using "insert()" // insert( group1 )( "Denmark", list_of(1)(1) ) ( "Germany", list_of(0)(0) ) ( "England", list_of(0)(1) ); BOOST_ASSERT( group1.size() == 3 ); BOOST_ASSERT( group1[ "Denmark" ][1] == 1 ); BOOST_ASSERT( group1[ "Germany" ][0] == 0 ); // // method 2: using "list_of()" // group2 = list_of< score_pair > ( "Norway", list_of(1)(0) ) ( "USA", list_of(0)(0) ) ( "Andorra", list_of(1)(1) ); BOOST_ASSERT( group2.size() == 3 ); BOOST_ASSERT( group2[ "Norway" ][0] == 1 ); BOOST_ASSERT( group2[ "USA" ][0] == 0 ); }
在第一个例子里,请注意list_of()生成的列表是如何自动被转换为一个 vector的,因为insert()并不知道一个vector的存在。第二个例子中,我们可以看到list_of()也许不够智能,它需要被显式地告知要求什么样的参数 (未来版本中,可能引入一个中间层来完成更为智能的转换。)。
全部内容就这么多。现在,你一定准备好try一try了。
3、参考
值得一提的是这个库执行的方式。 一个独立(free-standing)的函数(例如push_back()或者operator+=())返回一个负责插入或赋值的代理对象。这个代理对象通过重载operator,()、operator()()和调用操作符内部的insert函数执行插入或赋值操作。insert函数使用boost::function保存在这个代理对象中。
频繁重载operator,()有些令人沮丧,它可能导致意外的结果,但是我们采用的方式是安全的,用户无需直接处理包含重载operator,()的对象。 不过你应该知道这些:
逗号分隔列表中的表达式不再依照operator , 原本的规则。这意味着当用户指定一个函数参数列表的时候,在逗号分隔列表中表达式的求值顺序可能是未定义的。 文档中的大多数例子使用的是整型,但是它们当然可以使用任何类型,只要它们可以被拷贝构造。插入的数据不必是常量,也可以是变量或是函数返回值,唯一的要求是值的类型可以被转换为容器的值类型。
所有传递都是被局部对象以值传递的方式进行。我们最初使用const reference,但它会由于字符串常量导致很多的问题。有一点应该记住,reference能够被boost::ref传递。
关于库的所有细节都被封装在namespace boost::assign中。
更多的细节如下:
- 头文件
- 标准容器
- list_of() and map_list_of()
- repeat() and repeat_fun()
- list_inserter类
- make_list_inserter()
- 定制参数列表的大小
o header
下面概要列出库中的头文件。请注意<boost/assign/list_inserter.hpp>被每一个定义了operator+=()的头文件包含:
Header | Includes |
---|---|
<boost/assign.hpp> | everything |
<boost/assign/list_of.hpp> | list_of(), map_list_of() |
<boost/assign/std.hpp> | operator+=() for all standard containers (see below) |
<boost/assign/std/deque.hpp> | operator+=() for std::deque, <deque> |
<boost/assign/std/list.hpp> | operator+=() for std::list, <list> |
<boost/assign/std/map.hpp> | operator+=() for std::map and std::multimap , <map> |
<boost/assign/std/queue.hpp> | operator+=() for std::queue and std::priority_queue, <queue> |
<boost/assign/std/set.hpp> | operator+=() for std::set and std::multiset, <set> |
<boost/assign/std/slist.hpp> | operator+=() for std::slist if the class is available , <slist> |
<boost/assign/std/stack.hpp> | operator+=() for std::stack, <stack> |
<boost/assign/std/vector.hpp> | operator+=() for std::vector, <vector> |
<boost/assign/assignment_exception.hpp> | Class assignment_exception which might be thrown by the proxy returned by list_of() |
<boost/assign/list_inserter.hpp> | Functions make_list_inserter(), push_back(), push_front(),insert(), push() and class list_inserter which is the back-bone of this entire library. |
o standard container
如下的省略号 (...)意味着执行体被定义. operator+=() 返回一个代理对象进一步调用push_back(),insert(), 或是 push() ,这依赖于容器所支持的操作。
纲要
namespace boost { namespace assign { template< class V, class A, class V2 > list_inserter< ... > operator+=( std::deque<V,A>& c, V2 v ); template< class V, class A, class V2 > list_inserter< ... > operator+=( std::list<V,A>& c, V2 v ); template< class K, class V, class C, class A, class P > list_inserter< ... > operator+=( std::map<K,V,C,A>& m, const P& p ); template< class K, class V, class C, class A, class P > list_inserter< ... > operator+=( std::multimap<K,V,C,A>& m, const P& p ); template< class V, class C, class V2 > list_inserter< ... > operator+=( std::queue<V,C>& c, V2 v ); template< class V, class C, class V2 > list_inserter< ... > operator+=( std::priority_queue<V,C>& c, V2 v ); template< class K, class C, class A, class K2 > list_inserter< ... > operator+=( std::set<K,C,A>& c, K2 k ); template< class K, class C, class A, class K2 > list_inserter< ... > operator+=( std::multiset<K,C,A>& c, K2 k ); #ifdef BOOST_HAS_SLIST template< class V, class A, class V2 > list_inserter< ... > operator+=( std::slist<V,A>& c, V2 v ); #endif template< class V, class C, class V2 > list_inserter< ... > operator+=( std::stack<V,C>& c, V2 v ); template< class V, class A, class V2 > list_inserter< ... > operator+=( std::vector<V,A>& c, V2 v ); } // namespace "assign" } // namespace "boost"
注意额外的模板V2对于允许值类型转换到V是必要的.
o list_of() and map_list_of()
这两个函数用于构造匿名的列表(可以转换为任何标准容器甚至是boost::array<T,sz>)。这两个函数返回的对象被保证继承一下接口。
纲要
namespace boost { namespace assign { template< class T > class Implementation-defined { public: const_iterator begin() const; const_iterator end() const; template< class U > Implementation-defined& operator,( U u ); // inserts default-constructed object Implementation-defined& operator()(); template< class U > Implementation-defined& operator()( U u ); template< class U, class U2 > Implementation-defined& operator()( U u, U2 u2 ); // // and similarly up to 5 arguments // // // Convert to a "Container". "Container" must have a constructor // which takes two iterators. // template< class Container > operator Container() const; // // Convert to a container adapter like "std::stack<>". // Convertible-to-adapter to_adapter() const; // // // Convert to eg. "boost::array<T,std::size_t>". If the // assigned variable is too small, // a assignment_exception is thrown. // If the assigned variable it is too big, the rest of the // values are default-constructed. // template< template <class,std::size_t> class Array, class U, std::size_t sz > operator Array<U,sz>() const; }; template< class T > Implementation-defined list_of(); template< class T > Implementation-defined list_of( T t ); template< class T, class U, class U2 > Implementation-defined list_of( U u, U2 u2 ); template< class T, class U, class U2, class U3 > Implementation-defined list_of( U u, U2 u2, U3 u3 ); template< class T, class U, class U2, class U3, class U4 > Implementation-defined list_of( U u, U2 u2, U3 u3, U4 u4 ); template< class T, class U, class U2, class U3, class U4, class U5 > Implementation-defined list_of( U u, U2 u2, U3 u3, U4 u4, U5 u5 ); template< class Key, class T > Implementation-defined map_list_of( Key k, T t ) { return list_of< std::pair<Key,T> >()( k, t ); } } // namespace "assign" } // namespace "boost"
o repeat() and repeat_fun()
这两个函数既是独立的函数,同时也是list_of()或者list_inserter所返回的对象的成员函数。独立函数的版本用于建立一个到operator()的钩子,使得我们可以在逗号分隔列表中调用这个函数。在两种情况下我们都有:
- 对于所有函数,返回类型总是implementation-defined
- repeat()的参数是 (std::size_t,T)
- repeat_fun()的参数是(std::size_t,Nullary_function)
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……),只有一个头文件,缺点在于需要借助于正则表达式,编译时还需要导入库文件,会麻烦一些。