背景
在许多情况下,我们都需要为容器初始化或赋值,填入大量的数据,如初始错误代码和错误信息,或者是一些测试用的数据。在 C++98 中标准容器仅提供了容纳这些数据的方法,但填充的步骤却相当麻烦,必须重复调用 insert() 或 push_back() 等成员函数,这正是 boost.assign 出现的理由。
assign 库重载了赋值操作符 "+=",逗号操作符 "," 和括号操作符 "()",这样就可以用非常简洁的语法非常方便地对标准容器赋值或初始化,这在需要填入大量初值的地方很有用。C++新标准也提供了类似功能的初始化列表,但其功能没有 assign 库那么完备。
assign 库位于名字空间 boost::assign,需要包含的头文件如下:
cpp
#include <boost/assign.hpp>
using namespace boost::assign;
list_inserter
list_inserter 是 assign 库中用来操作容器的工具类,它类似 std::back_inserter,但 list_inserter 增加了很多操作符重载和助手类来简化代码。
list_inserter 的类摘要如下:
cpp
template<class Function>
class list_inserter
{
public:
list_inserter operator,(const T& r); //重载 operator,
list_inserter operator()(); //重载 operator()
list_inserter operator()(const T& r);
public: //重复输入数据操作
list_inserter repeat(std::size_t sz, T r); //重复输入
list_inserter repeat_fun(std::size_t sz, Nullary_function fun);
list_inserter range(SinglePassIterator first, SinglePassIterator last);
list_inserter range(const SinglePassRange& r);
private:
Function insert_;
};
list_inserter 内部存储了一个函数对象"insert_"用来操作容器,这个函数对象包装了容器的 push_back 和 push_front 等操作,例如:
cpp
list_inserter operator,(const T& r) //重载 operator,
{
insert_(r); //向容器添加元素
return *this; //返回自身的引用
}
list_inserter 成员函数的另一个特点是返回 *this,这使得它可以像标准流操作一样串联操作,以达到简化代码的目的。
list_inserter 还提供 repeat/range 函数来简化输入重复数据的工作。
operator+=
由于 list_inserter 重载了操作符 "+=" 和 ",",我们就可以用简洁到令人震惊的语法完成原来用许多代码才能完成的工作,如果不熟悉 C++ 操作符重载的原理你甚至都不会意识到在简洁语法下的复杂工作。
使用 assign 库时必须使用 using 指示符,只有这样才能让重载的 "+=" 和 "," 等操作符在作用域内生效。例如:
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/assign.hpp>
#include <vector>
using namespace std;
using namespace boost::assign;
int main()
{
cout << "Start" << endl;
vector<int> v; //标准向量容器
v += 1, 2, 3, 4, 5, 6 * 6; //用 "+=" 和 "," 填入数据
cout << "vector<int>:" << endl;
for (auto itor : v)
{
cout << itor << " ";
}
cout << endl;
set<string> s; //标准集合容器
s += "c", "cpp", "lua", "swift"; //用 "+=" 和 "," 填入数据
cout << "set<string>:" << endl;
for (auto itor : s)
{
cout << itor << " ";
}
cout << endl;
map<int, string> m; //标准映射容器
m += make_pair(1, "one"), make_pair(2, "two"); //用 "+=" 和 "," 填入数据
cout << "map<int,string>:" << endl;
for (auto itor : m)
{
cout << itor.first << "\t" << itor.second << endl;
}
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
vector<int>:
1 2 3 4 5 36
set<string>:
c cpp lua swift
map<int,string>:
1 one
2 two
End
上面的代码示范了 assign 库操作标准容器的能力。"+=" 操作符后可以接若干个可被容器容纳的元素,元素之间使用 "," 分隔。元素不一定是常量,也可以是表达式或函数调用,只要其结果能够转换成容器可容纳的类型即可。比较特别的是 map 容器,它必须使用辅助函数 make_pair() 来生成容器元素,单纯地用括号把 pair 的两个成员括起来是无效的。
实现原理
assign 库提供 3 个工厂函数:push_back()、push_front() 和 insert()。这些函数可作用于拥有同名成员函数的容器,接收容器实例作为参数,创建对应的 list_inserter 对象。
在名字空间 boost::assign 里,assign 库为标准容器重载了 operator+=,调用 push_back() 或 insert() 函数,具体形式如下:
cpp
inline list_inserter operator+=(C& c, V v)
{
return push_back(c)(v); //对于关联容器则是 insert 函数
}
operator+= 作用于容器时会调用工厂函数 push_back(),产生一个 list_inserter 对象,以这个对象为起点,随后的 operator() 和 operator, 就会依次被执行,使用 push_back() 或 insert() 向容器插入数据。由于 list_inserter 重载了很少使用的逗号操作符,所以函数的调用得到了极大的简化。
operator+= 很好用,但有一点遗憾,assign 库仅提供了对标准容器(vector、list、set 等)的重载,要操作其他类型的容器(如 Boost 容器)只能依据其原理自行实现。
operator()
operator+= 仅作用于标准容器,而且在处理 map 容器时也显得有些麻烦,所以我们可以直接使用工厂函数 insert()、push_front()、push_back(),利用它们返回的 list_inserter 对象来填入数据。
示范工厂函数 insert()、push_front()、push_back() 用法的代码如下:
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/assign.hpp>
#include <vector>
#include <forward_list>
using namespace std;
using namespace boost::assign;
int main()
{
cout << "Start" << endl;
vector<int> v;
push_back(v)(1)(2)(3)(4)(5); //使用 push_back() 工厂函数
cout << "vector<int>:" << endl;
for (auto itor : v)
{
cout << itor << " ";
}
cout << endl;
list<string> l;
push_front(l)("c")("cpp")("lua")("swift"); //使用 push_front() 工厂函数
cout << "list<string>:" << endl;
for (auto itor : l)
{
cout << itor << " ";
}
cout << endl;
forward_list<string> fl; //C++的单向链表容器
push_front(fl)("matrix")("reload"); //使用 push_front() 工厂函数
cout << "forward_list<string>:" << endl;
for (auto itor : fl)
{
cout << itor << " ";
}
cout << endl;
set<double> s;
insert(s)(3.14)(0.618)(1.732); //使用 insert() 工厂函数
cout << "set<double>:" << endl;
for (auto itor : s)
{
cout << itor << " ";
}
cout << endl;
map<int, string> m;
insert(m)(1, "one")(2, "two"); //使用 insert() 工厂函数
cout << "map<int,string>:" << endl;
for (auto itor : m)
{
cout << itor.first << "\t" << itor.second << endl;
}
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
vector<int>:
1 2 3 4 5
list<string>:
swift lua cpp c
forward_list<string>:
reload matrix
set<double>:
0.618 1.732 3.14
map<int,string>:
1 one
2 two
End
这段代码与使用 operator+= 没有太大区别。对于拥有 push_back() 或 push_front() 成员函数的容器(如 vector、list、deque),可以调用 assign::push_back() 或 assign::push_front();而对于 set 和 map,则只能使用 assign::insert()。
operator() 的好处是可以在括号中使用多个参数,这对于 map 这样由多个值组成的元素非常方便,避免了使用 make_pair() 函数。如果括号中没有参数,那么 list_inserter 将调用容器元素的默认构造函数填入一个默认值,而逗号操作符则不能这样做。
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/assign.hpp>
#include <vector>
#include <forward_list>
using namespace std;
using namespace boost::assign;
int main()
{
cout << "Start" << endl;
vector<int> v;
push_back(v)()()()()(); //使用 push_back() 工厂函数
cout << "vector<int>:" << endl;
for (auto itor : v)
{
cout << itor << " ";
}
cout << endl;
list<string> l;
push_front(l)()()()(); //使用 push_front() 工厂函数
cout << "list<string>:" << endl;
for (auto itor : l)
{
cout << itor << "1" << endl;
}
forward_list<string> fl; //C++的单向链表容器
push_front(fl)()(); //使用 push_front() 工厂函数
cout << "forward_list<string>:" << endl;
for (auto itor : fl)
{
cout << itor << "2" << endl;
}
set<double> s;
insert(s)()()(); //使用 insert() 工厂函数
cout << "set<double>:" << endl;
for (auto itor : s)
{
cout << itor << "3" << endl;
}
map<int, string> m;
insert(m)()(); //使用 insert() 工厂函数
cout << "map<int,string>:" << endl;
for (auto itor : m)
{
cout << itor.first << "\t" << itor.second << endl;
}
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
vector<int>:
0 0 0 0 0
list<string>:
1
1
1
1
forward_list<string>:
2
2
set<double>:
03
map<int,string>:
0
End
括号操作符也可以与逗号操作符等操作符混合使用,其写法更简单,有时甚至看起来不像是合法的 C++ 代码(但它的确是完全正确的 C++ 代码)。例如:
cpp
vector<int> v;
push_back(v),1,2,3,4,5;
push_back(v)(6),7,64 / 8,(9),10; // v = [1 2 3 4 5 6 7 8 9 10]
deque<string> d;
push_front(d)() = "breath","of","the","wild";
assert(d.size()==5); // d = ['wild', 'the', 'of', 'breath', '']
generic_list
list_inserter 解决了对容器的赋值问题,但有的时候我们需要在构造容器的时候就完成数据填充,这种方式较赋值更为高效。
C++11 标准引入了初始化列表 std::initializer_list,而 boost.assign 库则提供与其功能类似的 generic_list,generic_list 的类摘要如下:
cpp
template<class T>
class generic_list
{
public:
iterator begin() const; //类似容器的接口
iterator end() const;
bool empty() const;
size_type size() const;
generic_list& operator,(const T& u); //重载 operator,
generic_list& operator()(); //重载 operator()
generic_list& operator()(const T& u);
public:
generic_list& repeat(std::size_t sz, U u); //重复输入数据操作
generic_list& repeat_fun(std::size_t sz, Nullary_function fun);
generic_list& range(SinglePassIterator first, SinglePassIterator last);
generic_list& range(const SinglePassRange& r);
public:
operator Container() const; //容器转换
Container to_container(Container c) const;
adapter_converter to_adapter() const;
Adapter to_adapter(Adapter& a) const;
Array to_array(Array a) const;
};
与 list_inserter 类似,generic_list 也重载了逗号操作符和括号操作符,因为要在初始化时与容器互操作,所以它还增加了一些容器操作函数。
cpp
generic_list& operator,(const T& u) //重载 operator,
{
this->push_back(u); //用 push_back()添加元素
return *this; //返回自身的引用
}
初始化容器
assign 库提供 3 个工厂函数 list_of(), map_list_of()/pair_list_of() 和 tuple_list_of(),它们能够产生 generic_list 对象,我们就可以像使用 list_inserter 一样使用 operator() 和 operator, 来填充数据。
因为 generic_list 提供容器类型的隐式转型操作,所以它可以赋值给任意容器,当然我们也可以显式地调用容器转换函数。
list_of
list_of() 函数的声明如下:
cpp
inline generic_list<T> list_of();
inline generic_list<T> list_of(const T& t);
list_of() 的用法与之前的 insert(), push_back() 等函数很相似:
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/assign.hpp>
#include <vector>
#include <forward_list>
using namespace std;
using namespace boost::assign;
int main()
{
cout << "Start" << endl;
vector<int> v = list_of(1)(2)(3)(4)(5);
// v = [1, 2, 3, 4, 5]
cout << "vector<int>:" << endl;
for (auto itor : v)
{
cout << itor << " ";
}
cout << endl;
deque<string> d =(list_of("power")("bomb"), "phazon", "suit"); //注意括号和逗号的使用
// d = [power bomb phazon suit]
cout << "deque<string>:" << endl;
for (auto itor : d)
{
cout << itor << endl;
}
set<int> s = (list_of(10), 20, 30, 40, 50); //注意括号和逗号的使用
// s = {10 20 30 40 50}
cout << "set<int>:" << endl;
for (auto itor : s)
{
cout << itor << endl;
}
map<int, string> m = list_of(make_pair(1, "one"))(make_pair(2, "two"));
// m = [(1, "one") (2, "two")]
cout << "map<int,string>:" << endl;
for (auto itor : m)
{
cout << itor.first << "\t" << itor.second << endl;
}
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
vector<int>:
1 2 3 4 5
deque<string>:
power
bomb
phazon
suit
set<int>:
10
20
30
40
50
map<int,string>:
1 one
2 two
End
list_of() 函数可以全部使用括号操作符,也可以把括号与逗号结合起来使用,但使用后者时需要将整个 list_of 表达式用括号括起来,否则编译器会无法推导出 list_of 表达式的类型导致赋值失败。
上面的代码对应 C++初始化列表的代码如下:
cpp
vector<int> v = {1,2,3,4,5};
deque<string> d = {"power","bomb","phazon","suit"};
set<int> s = {10,20,30,40,50};
map<int, string> m = {{1, "one"},{2, "two"}};
两段代码相比,虽然 assign 库的代码略显麻烦,但胜在对 C++各版本的兼容性。
map_list_of/pair_list_of
使用 list_of() 处理 map 容器不是很方便,于是 map_list_of() 和 pair_list_of() 应运而生,map_list_of() 可以接收两个参数,然后自动构造 std::pair 对象插入 map 容器,pair_list_of() 纯粹是 map_list_of 的同义词,两者的用法和功能完全相同。
map_list_of() 和 pair_list_of() 的基本形式如下:
cpp
template<class Key, class T>
map_list_of(const Key& k, const T& t); //key,value
template<class F, class S>
pair_list_of(const F& f, const S& s) //first,second
{
return map_list_of(f, s);
}
下面的代码演示了 map_list_of 的用法:
cpp
map<int, int> ml = map_list_of(1, 2)(3, 4)(5, 6);
//ml = [(1, 2)(3, 4)(5, 6)]
map<int, string> m2 = map_list_of(1, "one")(2, "two");
//m2 = [(1, "one") (2, "two")]
tuple_list_of
tuple_list_of 用于初始化元素类型为 tuple 的容器,tuple 是 Boost 引入的一种新的容器或数据结构。
重复输入
在填充数据时会遇到输入重复数据的问题,如果用之前的方法要写大量的重复代码,很麻烦,也容易造成多写或少写的错误。list_inserter 和 generic_list 提供了成员函数 repeat()、repeat_fun() 和 range() 来减轻工作量。
repeat()、repeat_fun() 和 range() 函数的简要声明如下:
cpp
list& repeat(std::size_t sz, U u);
list& repeat_fun(std::size_t sz, Nullary_function fun);
list& range(SinglePassIterator first, SinglePassIterator last);
list& range(const SinglePassRange& r);
repeat() 函数把第二个参数作为要填入的值,重复第一个参数指定的次数,与 vector、deque 等容器的构造函数很相似;repeat_fun() 函数同样重复第一个参数的次数,但第二个参数是个无参的函数或函数对象,它返回填入的数值;range() 函数则可以把一个序列里的全部或部分元素插入另一个序列。
repeat()、repeat_fun() 和 range() 同样也返回列表 list_inserter 或 generic_list,所以可以将它们串联到 operator() 和 operator, 操作序列里。
示范 repeat()、repeat_fun() 和 range() 用法的代码如下:
cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <boost/assign.hpp>
#include <vector>
#include <forward_list>
using namespace std;
using namespace boost::assign;
int main()
{
cout << "Start" << endl;
vector<int> v = list_of(1).repeat(3, 2)(3)(4)(5);
//v = 1,2,2,2,3,4,5
cout << "vector<int>:" << endl;
for (auto itor : v)
{
cout << itor << " ";
}
cout << endl;
multiset<int> ms;
insert(ms).repeat_fun(5, &rand).repeat(2, 1), 10;
//ms = x,x,x,x,x,1,1,10
cout << "multiset<int>:" << endl;
for (auto itor : ms)
{
cout << itor << " ";
}
cout << endl;
deque<int> d;
push_front(d).range(v.begin(), v.begin() + 5);
//d = 3,2,2,2,1
cout << "deque<int>:" << endl;
for (auto itor : d)
{
cout << itor << endl;
}
cout << "End" << endl;
system("pause");
return 0;
}
运行结果:
cpp
Start
vector<int>:
1 2 2 2 3 4 5
multiset<int>:
1 1 10 41 6334 18467 19169 26500
deque<int>:
3
2
2
2
1
End
操作非标准容器
assign 库不仅支持标准容器(vector、string、deque、list、set、multiset、map、multimap),也对容器适配器提供了适当的支持,包括 stack、queue 和 priority_queue。
因为 stack、queue 和 priority_queue 这三个容器适配器没有 push_back/push_front 函数,所以 assign 库提供了 push() 函数来赋值,但 stack 可以使用 operator+=。初始化的 list_of 表达式最后需要使用 to_adapter() 成员函数来适配到非标准容器,如果使用逗号操作符还需要把整个表达式用括号括起来,才能使用点号操作符调用 to_adapter()。
示范 assign 库应用于容器适配器的代码如下:
cpp
stack<int> stk = (list_of(1), 2, 3).to_adapter();
stk += 4, 5, 6;
for(; !stk.empty();) //输出 stack 的内容
{
cout << stk.top() << " ";
stk.pop();
}
queue<string> q = (list_of("china")("us")("uk"))
.repeat(2, "russia").to_adapter();
push(q)("germany");
for(; !q.empty();) //输出 queue 的内容
{
cout << q.front() << " ";
q.pop();
}
priority_queue<double> pq = (list_of(1.414), 1.732, 2.236).to_adapter();
push(pq), 3.414, 2.71828;
for(; !pq.empty();) //输出优先队列的内容
{
cout << pq.top() << " ";
pq.pop();
}
程序运行结果如下:
cpp
6 5 4 3 2 1
china us uk russia russia germany
3.414 2.71828 2.236 1.732 1.414
assign 库也支持大部分 Boost 库的容器,如 array、circular_buffer、unordered 等,只要它们有 push_back/insert 等成员函数,且其用法与标准容器基本类似即可
其他议题
assign 库还有两个类似功能的函数:ref_list_of() 和 cref_list_of(),这两个函数接收变量的引用作为参数来创建初始化列表,较 list_of() 的内部 deque 效率更高,但其用法略显麻烦。例如:
cpp
int a = 1, b = 2, c = 3;
vector<int> v = ref_list_of<3>(a)(b)(c);
assert(v.size() == 3);
assign 库还支持 Boost 库中的指针容器,提供 ptr_push_back()、ptr_list_of() 等函数。