65.9K
CodeProject 正在变化。 阅读更多。
Home

非常好用的初始化库

starIconstarIconstarIconstarIconstarIcon

5.00/5 (26投票s)

2004年3月4日

6分钟阅读

viewsIcon

115649

downloadIcon

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_initializerMap_comma_initializer是包装了push_back()insert()等常规成员函数的类,并通过调用operator,()来实现。这样,所有容器的接口都将相同。重载逗号运算符有时被认为是不好的做法[3]。然而,它已成功用于例如生成矩阵计算库和Blitz来初始化矩阵(参见[4])和[5])。初始化库通过让set_contset_map返回一个负责初始化的对象来安全地重载逗号运算符。因此,程序员需要采取明确的行动来开始使用重载的operator,()

所有enum()函数都用于生成数值类型的序列。这意味着内置类型、complexboost::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_typepair< 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 ZolmanSTL容器初始化库。Leor Zolman也提供了有益的反馈。

下载

  • init.hpp - 主头文件(短期内将变为多个文件)
  • tst.cpp - 测试程序(无法单独编译,请在此处下载Leor Zolman的其他实用程序)

参考文献

  1. Boost的array类确实使数组更加方便。
  2. http://www.bdsoft.com/tools/initutil.html
  3. Scott. Meyers,“More Effective C++”,第7项,Addison Wesley,1996年
  4. K. Czarnecki和U.W. Eisenecker,“Generative programming”,Addison-Wesley,2000年
  5. http://www.oonumerics.org/blitz/
© . All rights reserved.