如何快速自己做网站营销推广48个方法
operators
C++提供了强大且自由的操作符重载能力,可以把大多数操作符重新定义为函数,使操作更加简单直观。这方面很好的例子就是标准库中的string和 complex,可以像操作内置类型int、double那样对它们进行算术运算和比较运算,非常方便。
但实现重载操作符却比使用它要麻烦许多,因为很多运算具有对称性,如果定义了operator+,那么很自然需要operator-,如果有小于比较,那么也应该有小于等于、大于、大于等于比较。完全实现这些操作符的重载工作是单调乏味的,而且增加的代码量也增加了出错的可能性,还必须保证这些操作符都实现了正确的语义。
实际上很多操作符可以从其他的操作符自动推导出来,例如a !=b可以是!(a==b),a>=b可以是!(a<b)。因此原则上只需要定义少量的基本操作符,其他的操作符就可以用逻辑组合实现。
在c++标准的std::rel_ops名字空间里提供了四个模板比较操作符 !=、>、<=、>=,只需要为类定义了==和<操作符,那么这四个操作符就可以自动实现。
#include <utility>
class demo_class //一个定义operator<的类
{
public:demo_class(int n) :x(n) {}int x;friend bool operator<(const demo_class& l, const demo_class& r){return l.x < r.x;}
};void case1()
{demo_class a(10), b(20);using namespace std::rel_ops; //打开std::rel_ops名字空间assert(a < b); //自定义的<操作符assert(b >= a); //>=等操作符被自动实现
}
但std::rel_ops的解决方案过于简单,还很不够。除了比较操作符,还有很多其他的操作符重载标准库没有给出解决方案,而且使用这些操作符需要用using 语句导入std::rel_ops名字空间,不方便,也会带来潜在的冲突风险。
boost.operators库因此应运而生。它采用类似std::rel_ops 的实现手法,允许用户在自己的类里仅定义少量的操作符(如<),就可方便地自动生成其他操作符重载,而且保证正确的语义实现。
operators位于名字空间boost,为了使用operators组件,需要包含头文件<boost/operators.hpp>,即:
#include <boost/operators.hpp>
using namespace boost;
基本运算概念
由于C++可重载的操作符非常多,因此 operators库是由多个类组成的,分别用来实现不同的运算概念,比如 less_than_comparable定义了<系列操作符,left_shiftable定义了<<系列操作符。
operators中的概念很多,包括了C++中的大部分操作符重载,在这里我们先介绍一些最常用的算术操作符:
equality_comparable : 要求提供==, 可自动实现!=, 相等语义;
less_than_comparable : 要求提供<, 可自动实现>、<=、>=:
addable : 要求提供+=, 可自动实现+;
subtractable : 要求提供-=, 可自动实现-;
incrementable : 要求提供前置++, 可自动实现后置++;
decrementable : 要求提供前置--, 可自动实现后置--;
equivalent : 要求提供<, 可自动实现-=, 等价语义。
这些概念在库中以同名类的形式提供,用户需要以继承的方式来使用它们。继承的修饰符并不重要(private、public都可以),因为 operators库里的类都是空类,没有成员变量和成员函数,仅定义了数个友元操作符函数。
例如,less_than_comparable的形式是:
template <class T>
struct less_than_comparable {friend bool operator> (const T& x, const T& y);friend bool operator<= (const T& x, const T& y);friend bool operator>= (const T& x, const T& y);
};
如果要同时实现多个运算概念则可以使用多重继承技术,把自定义类作为多个概念的子类,但多重继承在使用时存在很多问题,稍后将看到operators库使用了特别的技巧来解决这个问题。
算术操作符的用法
class point :
{int x, y, z;
public:explicit point(int a = 0, int b = 0, int c = 0) :x(a), y(b), z(c) {}void print()const{cout << x << "," << y << "," << z << endl;}
};
我们先来实现less_than_comparable,它要求point类提供<操作符,并由它继承。假定point的小于关系是由三个坐标值的平方和决定的,下面的代码示范了less_than_comparable的用法,只需要为point增加父类,并定义less_than_comparable概念所要求的operator<:
class point : boost::less_than_comparable<point> //小于关系, 私有继承
{int x, y, z;
public:explicit point(int a = 0, int b = 0, int c = 0) :x(a), y(b), z(c) {}void print()const{cout << x << "," << y << "," << z << endl;}friend bool operator<(const point& l, const point& r){return (l.x * l.x + l.y * l.y + l.z * l.z <r.x* r.x + r.y * r.y + r.z * r.z);}
... //其他成员函数
};
less_than_comparable作为基类的用法可能稍微有点奇怪,它把子类point作为了父类的模板参数:less_than_comparable<point>
,看起来好像是个“循环继承”。实际上,point类作为less_than_comparable的模板类型参数,只是用来实现内部的比较操作符,用做操作符函数的类型,没有任何继承关系。less_than_comparable生成的代码可以理解成这样:
//template<T = point>
struct less_than_comparable
{friend bool operator>=(const point& x, const point& y){ return !(x < y); }
}
明白了less_than_comparable 的继承用法,剩下的就很简单了:point类定义了一个友元operator<操作符,然后其余的>、<=、>=就由less_than_comparable自动生成。几乎不费什么力气,在没有污染名字空间的情况下我们就获得了四个操作符的能力:
int main()
{point p0, p1(1, 2, 3), p2(3, 0, 5), p3(3, 2, 1);assert(p0 < p1&& p1 < p2);assert(p2 > p0);assert(p1 <= p3);assert(!(p1 < p3) && !(p1 > p3));
}
同样我们可以定义相等关系,使用equality_comparable,规则是point的三个坐标值完全相等,需要自行实现operator==:
class point : boost::less_than_comparable<point>, //使用多重继承boost::equality_comparable<point> //新增相等关系
{
public:friend bool operator<(const point& l, const point& r){ /*同前*/ }friend bool operator==(const point& l, const point& r){ return r.x == l.x && r.y == l.y && r.z == l.z; }
};
然后我们就自动获得了operator!=定义:
point p0, p1(1,2,3), p2(p1), p3(3,2,1);
assert(p1 == p2);
assert(p1 != p3);
在使用operators库时要注意一点,模板类型参数必须是子类自身,特别是当子类本身也是个模板类的时候,不要错写成子类的模板参数或者子类不带模板参数的名称,否则会造成编译错误。假如我们改写point类为一个模板类:
template<typename T> class point {...};
那么如下的形式都是错误的:
template<typename T> class point: boost::less_than_comparable<T>
template<typename T> class point: boost::less_than_comparable<point>
正确的写法应该是:
template<typename T> class point: boost::less_than_comparable<point<T>>
因为只有point<T>
才是模板类point的全名。
基类链
多重继承一直是C++中引发争论的话题,喜欢它的人和讨厌它的人几乎同样多。总的来说,多重继承是一种强大的面向对象技术,但使用不当也很容易引发诸多问题,比如难以优化和经典的“钻石型”继承。
operators库使用泛型编程的“基类链”技术解决了多重继承的问题,这种技术通过模板把多继承转换为链式的单继承。
前面当讨论到 less_than_comparable<point>
这种用法时,我们说它不是继承,然而,现在,我们将看到它居然真的可以实现继承的功能,这从一个方面展示了泛型编程的强大威力。
operators库的操作符模板类除了接受子类作为比较类型外,还可以接受另外一个类,作为它的父类,由此可以无限串联链接在一起(但要受编译器的模板编译能力限制),像这样:
demo: x<demo, y<demo, z<demo, ...> > >
使用基类链技术,point类的基类部分可以是这样:
boost::less_than_comparable<point, //注意这里
boost::equality_comparable<point>> //是一个有很大模板参数列表的类
对比一下多重继承的写法
boost::less_than_comparable<point>, //注意这里
boost::equality_comparable<point> //有两个类
代码非常相似,区别仅仅在于模板参数列表结束符号(>)的位置,如果不仔细看可能根本察觉不出差距。但正是这个小小的差距,使基类链通过模板组成了一连串的单继承链表,而不是多个父类的多重继承。
例如,如果为point类再增加加法和减法定义,则继承列表就是:
class point:less_than_comparable<point, //小于操作equality_comparable<point, //相等操作addable<point, //相加操作subtractable<point //减法操作> > > >
{...};
基类链技术会导致代码出现一个有趣的形式:在派生类的基类声明末尾处出现一长串的>(模板声明的结束符),在编写代码时需要小心谨慎以保证尖括号的匹配,使用良好的代码缩进和换行可以减少错误的发生。