当前位置: 首页 > news >正文

Boost开发指南-4.8operators

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 //减法操作> > > >
{...};

基类链技术会导致代码出现一个有趣的形式:在派生类的基类声明末尾处出现一长串的>(模板声明的结束符),在编写代码时需要小心谨慎以保证尖括号的匹配,使用良好的代码缩进和换行可以减少错误的发生。

http://www.lryc.cn/news/135427.html

相关文章:

  • c# 泛型约束
  • android frida
  • Linux下的Shell编程——正则表达式入门(四)
  • 使用VisualStudio制作上位机(一)
  • 【前端从0开始】JavaSript——自定义函数
  • 如何在Windows、Mac和Linux操作系统上安装Protocol Buffers(protobuf)编译器
  • 简单介绍 CPU 的工作原理
  • UE4/5数字人MetaHuman的控制绑定资产使用
  • 二、11.系统交互
  • 敏捷管理工具/国内软件敏捷开发工具
  • Selenium环境+元素定位大法
  • Vue3 用父子组件通信实现页面页签功能
  • HCIP STP协议
  • 链表的顶级理解
  • 探索贪心算法:理解与实现JAVA语言
  • 数字孪生技术对旅游行业能起到什么作用?
  • 攻防世界-Web_php_include
  • Python Opencv实践 - 直方图显示
  • 2分钟搭建自己的GPT网站
  • deepdiff比较两个json文件数据差异性
  • 文件内容搜索工具 - Python实现
  • vue静态html加载外部组件
  • WebSocket 中的心跳是什么,有什么作用?
  • Android类加载机制
  • 微信小程序列表加载更多
  • 数据库知识
  • VUE 目录介绍
  • Selenium的基本使用
  • 数据结构-----树的易错点
  • 写之前的项目关于使用git remote -v 找不到项目地址的解决方案