非常好用的初始化库





5.00/5 (26投票s)
2004年3月4日
6分钟阅读

115649

1232
您是否厌倦了手动向STL容器填充数据?使用初始化库会使这一切变得容易得多。
前言
作者提醒读者,有关此库及其实现的更深入描述出现在2003年10月的《C/C++ 用户杂志》上,文章标题为“非侵入式容器初始化”。本文中介绍的部分已得到改进,并正在Boost(参见https://boost.ac.cn/)进行审查。关于枚举范围的另一部分未在本文中介绍。具有模板化转换运算符对某些编译器来说仍然可能很棘手,但Leor Zolman在他的初始化库(参见http://www.bdsoft.com/tools/initutil.html)中已将其移植到各种编译器。请注意,通过调用container.insert( container.end(), new_value ) )
可以大大简化转发机制。
9月25日:一个包含本文大部分想法的新型优秀库现已作为boost 1.32的一部分发布(参见www.boost.org)。该库名为boost.assign,可以在https://boost.ac.cn/libs/assign/doc/index.html找到。
目录
引言
您是否厌倦了手动向STL容器填充数据?使用初始化库会使这一切变得容易得多。您可以简单地说
vector<int> primes; set_cont( v ) += 1, 2, 3, 5, 7, 11; list<int> ten_odd = enum_n( 10, 2 ); // '{ 1,3,5,7,9,11,13,15,17,19 }' map<const char*,int> months; set_map( 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; class Widget; deque<Widget> dw; set_cont( dw ) += Widget( 2, 2 ), Widget( "name", 2 );
关键思想是以一种非侵入式且便捷的方式重载operator,()
。一些便捷函数可用于创建枚举或生成的数据。
动机
该库存在的原因可以总结如下:
快速轻松地初始化STL容器。
如果需要用常量数据填充STL容器,这是一项相当繁琐的工作。这种情况在学习、测试和原型设计中可能很常见。在其他语言中,这项工作通常很容易。例如,在Perl中,您可以说
@date = (8, 24, 70); %fruit = ('apples', 3, 'oranges', 6);
分别赋给数组或映射。数组语法在C++中几乎相同,但由于内置数组不太方便,因此通常会将数据存入STL容器[1]。在C++中,您可以依赖两种方法[2]:
- 使用临时数组
- 使用容器的成员函数
第一种方法例如是
const int N = 6; const char* a[N] = { "isomer", "ephemeral", "prosaic", "nugatory", "artichoke", "serif" }; set<const char*>c A( a, a + N );
这需要手动管理数组大小。后一种方法更糟糕
vector<int> v; v.push_back( 1 ); v.push_back( 2 ); ...
显然,使用类似Perl的语法会更好。此外,如果它适用于任意类型的容器,而不仅仅是内置类型的容器,那就更好了。
摘要
我们首先介绍接口,然后讨论实现细节。为了使摘要更容易使用,模板和命名空间参数已从返回类型中隐藏;好奇者可以查看init.hpp。接口相当小。
namespace init { template< typename Container > Comma_initializer set_cont( Container& c ); template< typename Container > Map_comma_initializer set_map( Container& c ); ////////////////////////////////////////////// // enums ////////////////////////////////////////////// template< typename T > Any_container enum_n( size_t n ); template< typename T > Any_container enum_n( size_t n, T step_size ); template< typename T > Any_container enum_n_from( size_t n, T from ); template< typename T > Any_container enum_n_from( size_t n, T from, T step_size ); template< typename T, typename Functor > Any_container n_generated_by( T n, Functor generator ); } // namespace 'init'
Comma_initializer
和Map_comma_initializer
是包装了push_back()
和insert()
等常规成员函数的类,并通过调用operator,()
来实现。这样,所有容器的接口都将相同。重载逗号运算符有时被认为是不好的做法[3]。然而,它已成功用于例如生成矩阵计算库和Blitz来初始化矩阵(参见[4])和[5])。初始化库通过让set_cont
和set_map
返回一个负责初始化的对象来安全地重载逗号运算符。因此,程序员需要采取明确的行动来开始使用重载的operator,()
。
所有enum()
函数都用于生成数值类型的序列。这意味着内置类型、complex
、boost::rational
或具有类似行为的类型。enum()
函数都提供两种形式:一种是以1
为间隔枚举数字,另一种允许用户指定间隔大小。Any_container
是一个隐式转换为库支持的所有容器的类。下表还显示了重载的逗号运算符转发到哪个成员函数。
标准容器 |
|
---|---|
向量 | push_back() |
列表 | push_back() |
双端队列 | push_back() |
set | insert() |
multiset | insert() |
map | insert() |
multimap | insert() |
stack | push() |
queue | push() |
priority_queue | push() |
SGI容器扩展 | |
slist | insert() |
hash_set | insert() |
hash_multiset | insert() |
hash_map | insert() |
hash_multimap | insert() |
Boost容器 | |
数组 | 不可用 |
下面是对每个函数的解释。
-
Comma_initializer set_cont( Container& c );
set_cont
是“set container”的缩写。该库的大部分功能都可以通过此函数获得。使用它需要两个步骤:创建容器并对容器调用该函数。vector<float> vf; set_cont( vf ) += 1.f, 3.f, 5.f; set_cont( vf ) = 1.f;
使用
operator+=()
和operator=()
之间的区别与内置类型相同。前者添加到容器,而后者在添加新元素之前清除容器。
值得注意的是,模板代码允许初始化任何类型的容器,前提是该类型满足复制构造和复制赋值的通用要求。将直接调用构造函数,并用匿名对象初始化容器。
也可以使用当前不支持但具有push_back()
成员函数的容器。原因是push_back()
是默认的插入策略,并且为所有其他容器编写了特化。否则,必须为自己的容器创建Insert_policy
类的部分特化。 -
Map_comma_initializer set_map( Container& c );
该函数提供与set_cont()
相同的接口,除了它是为映射类设计的。使用它有点不同,因为需要指定键-数据对。map<string,int> m; set_map( m ) += "fish", 1, string( "horse" ), 2; set_map( m ) = "cow", 1;
和以前一样,
operator=()
会先重置容器。同样,代码将适用于满足容器要求的任意类型。如果出现错误,例如set_map( m ) += "fish", "fish", 2, 3;
由于键-数据交替错误,断言将在运行时触发。
如果需要使用自定义映射容器,只要映射类支持insert( const value_type& )
,其中value_type
是pair< const key,data > )
,它就会立即工作。否则,需要为Map_insert_policy
创建部分特化以支持自定义容器。 -
Any_container enum_n( size_t n );
Any_container enum_n( size_t n, T step_size );
enum_n
是“enumerate n elements”的缩写。第一个容器将包含n个元素。{ 1, ..., n }
第二个容器将包含n个元素。
{ 1 + 0*step_size, ..., 1 + (n-1)*step_size } .
在单参数版本中,必须显式地将生成值的类型指定为模板参数。
通过指定负step_size
可以生成反向序列。
示例vector<int> vi = enum_n<int>( 3 ); // 1,2,3 assert( v.size() == 3 ); vector<float> vf = enum_n<float>( 5 ); stack<double> sd = enum_n( 4, 2.1 ); // 1.0, 3.1, 5.2, 7.3 assert( sd.top() == 7.3 ); boost::array<int,3> a = enum_n( 3, -1 ); // 1, 0, -1
-
Any_container enum_n_from( size_t n, T from );
Any_container enum_n_from( size_t n, T from, T step_size );
enum_n_from
是“enumerate n elements starting from”的缩写。第一个容器将包含n个元素。{ from + 0, ..., from + (n-1) }
第二个容器将包含n个元素。
{ from + 0*step_size, ...,from + (n-1)*step_size }
您不需要指定模板参数,因为包含的类型将从第二个参数推导出来。
通过指定负step_size
可以生成反向序列。
示例list<int> = enum_n_from( 4, 4 ); // 4,5,6,7 queue<complex> qc = enum_n_from( 2, complex( 2, 0 ), complex( 1 , 2 ) ); // (2,0), (3,2), (4,4) vector<int> vi = enum_n_from( 5, 5, -1 ); // 5, 4, 3, 2, 1
-
Any_container
n_generated_by( T n, Functor generator );
调用生成器n次并将结果存储在容器中。该函数只是包装了std::generate算法。这意味着:vector<int> v = n_generated_by( 10, some_functor() );
等同于:
vector<int> v( 10 ); generate( v.begin(), v.end(), some_functor() );
并且使用算法的要求保持不变。特别是,生成器必须返回类型T并且不接受任何参数。注意,客户端需要将包含的类型作为n的类型提供。例如:
vector<float> f = n_generated_by( 10.f, &rand ); .
致谢
初始化库的这个想法并不新颖。该库的功能在很大程度上类似于Leor Zolman的STL容器初始化库。Leor Zolman也提供了有益的反馈。
下载
参考文献
- Boost的array类确实使数组更加方便。
- http://www.bdsoft.com/tools/initutil.html
- Scott. Meyers,“More Effective C++”,第7项,Addison Wesley,1996年
- K. Czarnecki和U.W. Eisenecker,“Generative programming”,Addison-Wesley,2000年
- http://www.oonumerics.org/blitz/