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

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

复合运算概念

基类链技术解决了多重继承的效率问题,但它也带来了新的问题,为了使用操作符概念需要写出很长的基类链代码。因此 operators库使用基类链把一些简单的运算概念组合成了复杂的概念,即复合运算。这是个很自然的要求,如果有<,则当然会需要==,如果有了+,则可能还需要-、*和/。复合运算不仅进一步简化了代码的编写,给出了更明确的语义,它也可以避免用户代码中基类链过长的问题。

operators库提供的常用复合运算概念如下:

totally_ordered : 全序概念, 组合了equality_comparable和less_than_comparable
additive : 可加减概念, 组合了addable和subtractable
multiplicative : 可乘除概念, 组合了multipliable和dividable
arithmetic : 算术运算概念, 组合了additive和multiplicative
unit_stoppable 可步进概念, 组合了incrementable和decrementable

使用复合运算概念,point类只需要很少的代码就可以很容易地获得完全的算术运算能力:

class point:
  totaily_ordered<point,  //全序比较运算
  additive<point> >  //可加减运算
{
    
    
public:
   friend bool operator<(const point& l, const point& r){
    
     ...}
   friend bool operator-- (const point& l, const point& r)i ...]
   point& operator+=(const point& r)  //支持addable概念
   {
    
    
       x += r.x;
       y += r.y;
       z += r.z;
       return *this;
   }
   point& operator-=(const point& r) //支持subtractable概念
   {
    
    
       x -= r.x;
       y -= r.y;
       z -= r.z;
       return *this;
   }
};

point的操作符重载验证代码如下:

int main()
{
    
    
	point p0, p1(1, 2, 3), p2(5, 6, 7), p3(3, 2, 1);

	using namespace boost::assign;
	vector<point> v = (list_of(p0), p1, p2, p3);

	auto pos = std::find(v.begin(), v.end(), point(1, 2, 3));
	pos->print();

	(p1 + p2).print();
	(p3 - p1).print();

	assert((p2 - p2) == p0);

}

point类很好地示范了复合运算操作符的用法:一般情况下,类型T继承boost::totally_ordered<T>,再定义<、==操作符即可获得完全的比较运算功能,能够用于标准容器和算法。

operators库另一个例子是rational类,它实现了有理数,支持全序和算术运算,不过很可惜的是它虽然使用了operators库,但没有用到复合运算概念,从而导致基类声明有16个“>”。

相等与等价

相等与等价是两个极易被混淆的概念。一个简单快速的解释是:相等基于操作符==,即x==y;而等价基于<,即 !(x<y) && ! (x>y),两者在语义上有很大差别。
对于简单类型(如int),相等和等价两者是一致的,例如5==10/2! (5<10/2) && !(5>10/2)。但对于大多数复杂类型和自定义类型,由于==<操作符是两个不同的运算,比较原则可能不同,从而两者具有不同的意义。

之前的point类是一个很好的例子。p1(1,2,3)和p3(3,2,1)两者完全不相等,但等价,因为等价运算使用的是operator<,它比较依据的是成员变量的平方和:1+2*2+3* 3==3*3+2*2+1

operators库使用equality_comparable和 equivalent明确地区分了相等与等价这两个概念。equality_comparable基于==,equivalent则基于<。但令人困扰的是它们最终都提供了操作符==,表现相同但含义非常不同。

了解相等与等价的区别非常重要,特别是当自定义类被用做容器的元素的时候。标准库中的关联容器(set、map)和排序算法使用的是等价关系的<操作符,而unordered_set/map和各种find查找算法使用的是相等关系的==操作符。

point类使用不同的规则定义了==和<,可以获得正确的比较和相等语义,下面是应用于标准容器的示范代码:

void case4()
{
    
    
	point p0, p1(1, 2, 3), p2(5, 6, 7), p3(3, 2, 1);

	using namespace boost::assign;
	vector<point> v = (list_of(p0), p1, p2, p3);

	auto pos = std::find(v.begin(), v.end(), point(1, 2, 3)); //使用相等语义查找元素
	for (; pos != v.end(); //查找下一个相等的元素
		pos = std::find(pos + 1, v.end(), point(1, 2, 3)))
	{
    
    
		pos->print(); //1,2,3
	}

	pos = std::find(v.begin(), v.end(), point(2, 1, 3));
	assert(pos == v.end());
}

这段代码将只找到p1(1,2,3),并且最后一个assert 断言成立,找不到值为(2,1,3)的point对象。

如果我们改变point 的定义,不使用equality_comparable,而改用equivalent来实现==操作符(等价语义),那么我们不必单独定义==操作符,它将由equivalent自动用<操作符来实现。上面的测试代码的行为将完全不同,它会输出两个点:p1(1,2,3)和p3(3,2,1),并且断言失败。

在使用关联容器set和map时更需要留意,它们仅要求<操作符,因此是基于等价语义的,即使使用equality_comparable定义了==操作符,把 point对象放入 set 或 map也会产生equivalent的效果。

请读者谨慎地考虑自定义类需要什么样的语义,如果只关心类的等价语义,那么就用equivalent,如果想要精确地比较两个对象的值,就使用equality_comparable。

解引用操作符

operators库使用dereferenceable提供了对解引用操作符*、->的支持,它的用法与之前介绍的算术操作符不太相同。

dereferenceable的类摘要如下:

template <class T, class P, class B =...>
struct dereferenceable : B
{
    
    
   P operator->() const;
};

dereferenceable有三个模板参数:
1)第一个参数T是要实现 operator->的子类,它的含义与算术操作符类相同
2)第二个参数Р是operator->所返回的类型,也就是指针类型,通常应该是T*
3)最后一个参数B是用于基类链技术的父类,实际使用时我们不需要关心

dereferenceable类要求子类提供operator*,会自动实现operator->。注意它的operator->函数的定义,不是如其他算术操作符类那样的友元函数,因此在使用dereferenceable时必须使用public继承,否则operator->将会成为类的私有成员函数,外界无法访问。

由于 dereferenceable实现了解引用操作符的语义,因此它可以用于实现自定义的智能指针类,或者是实现代理模式,包装代理某些对象。

例如,下面的代码实现了一个简单的智能指针类my_smart_ptr,它 public继承了dereferenceable,重载operator*并自动获得了operator->的定义:

template<typenameT>
class my_smart_ptr:
   public dereferenceable<my_smart_ptr<T>, T*> //必须public继承
{
    
    
    T *p; //内部保存的指针
public:
    my_smart_ptr(T*X): p(x) {
    
    } //构造函数
    ~my_smart_ptr() {
    
    delete p;} //析构函数
    T& operator*()const  // operator*定义,必须是常函数
    {
    
     return *p; }
};

my_smart_ptr的用法就像是scoped_ptr:

my_smart_ptr<string> p (new string ("123"));
assert(p->size() == 3);

下标操作符

operators库使用indexable提供了下标操作符[]的支持,它也属于解引用的范畴,用法与dereferenceable很相似,类摘要如下:

template <class T, class I, class R, class B>
struct indexable : B
{
    
    
    R operator[](I n) const;
};

indexable模板参数列表中的T和B含义与dereferenceable的T和B含义相同,分别是子类类型和基类链的父类类型。

参数工是下标操作符的值类型,通常应该是整数,但也可以是其他类型,只要它能够与类型T做加法操作。参数R是operator[]的返回值类型,通常应该是一个类型的引用。

indexable要求子类提供一个operator+(T,I)的操作定义,类似于一个指针的算术运算,它应该返回一个迭代器类型,能够使用operator*解引用得到R类型的值。

与dereferenceable 的例子类似,我们使用一个my_smart_array 类来模仿实现scoped_array。my_smart_array 从 indexable 公开继承,下标操作符类型是 int,operator[]的返回值类型是T&:

template<typename T>
class my_smart_array:
  public indexable<my_smart_array<T>, int, T& >
{
    
    
   T*p; //保存动态数组指针
public:
   typedef my_smart_array<T> this_type;
   typedef T* iter_type; //迭代器类型
   my_smart_array(T *x):p(x){
    
    } //构造函数
   ~my_smart_array() {
    
     delete[] P; } //析构函数
   friend iter_type operator+(const this_type& a, int n)
   {
    
    
       return a.p + n; //返回一个迭代器,可以使用operator*操作
   }
};

由于 my_smart_array 实现了operator+,因此它支持算术运算,又由于它继承自indexable,所以它还自动获得了operator[]的定义:

int main()
{
    
    
	my_smart_array<double> ma(new double[10]);
	ma[0] = 1.0; //operator[]
	*(ma + 1) = 2.0; //指针算法运算
	cout << ma[1] << endl; //输出2.0
}

bool转型操作符

转型操作符是C++中的一类特殊操作符,而 operator bool则更具有特殊性,如果简单地实现bool转型,那么会因为隐式转换在比较操作时会发生意想不到的问题。

下面的两个类定义了operator bool,因为可以隐式转换为bool,可以写出奇怪的比较代码:

struct demo_a //隐式转换为true
{
    
    
	bool operator!() const
	{
    
    
		return false;
	}
};

struct demo_b //隐式转换为false
{
    
    
	bool operator!() const
	{
    
    
		return true;
	}
};

int main()
{
    
    
	demo_a a;
	demo_b b;
	assert(a != b); //隐式转换为bool执行比较
	assert(a > b); //true > false
}

为了避免这类尴尬的问题我们可以使用safe bool 惯用法,定义一个到unspecified_bool函数指针类型的转换4,而C++11则在语法层面引入了显式转型操作符的概念(使用explicit修饰)。Boost程序库提供工具 explicit_operator_bool,可以根据编译器对C++11标准的支持程度选择最恰当的解决方案。

explicit_operator_bool不属于operators库,而是core库的一部分,它位于头文件<boost/core/explicit_operator_bool.hpp>

explicit_operator_bool要求类实现operator !() const,提供三个操作符定义宏:

#define BOOST_EXPLICIT_OPERATOR_BOOL() //安全bool转型
#define BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT() //异常保证的bool转型
#define BOOST_CONSTEXPR_EXPLICIT_OPERATOR_BOOL() //编译期常量bool转型

我们只需要根据具体情况实现自己的operator!重载,再选择一个宏即可实现安全bool转型,例如:

#include <boost/core/explicit_operator_bool.hpp>

struct demo_a
{
    
    
	BOOST_EXPLICIT_OPERATOR_BOOL() //定义显示bool转型操作符
		bool operator!() const //定义operator!
	{
    
    
		return false;
	}
};

struct demo_b
{
    
    
	BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT() //定义显示bool转型操作符
		bool operator!() const //定义operator!
	{
    
    
		return true;
	}
};

int main()
{
    
    
	demo_a a;
	demo_b b;
	assert(a && !b);
	a > b; //编译错误
}

二元操作符

二元操作符(如-、+)的两个参数一般是同类型的,但有时候也可能会是不同类型的。比如 point,可以允许它与一个整型的标量做加减法,其效果是每个坐标值对标量做加减法。

operators库提供了使用两个模板类型参数的概念类,用来支持这种用法,例如less_than_comparable<T, U>。对于不支持模板偏特化的编译器,operators 为每个操作符提供额外的两种形式,增加后缀“1”和“2”。如果程序可能在不同的编译器上编译,则为了兼容请使用带扩展的模板。例如:

class point:
  boost::totally_ordered<point,
  boost::addable1<point>, //一个模板参数的概念类
  boost::addable2<point, int> > //两个模板参数的概念类
{
    
    
public:
   ...
   point& operator+=(const int& r)
   {
    
    
       x += r;
       y += r;
       z += r;
       return *this;
   }
   ...
}

代码示例

#include <utility>
#include <iostream>
#include <assert.h>
using namespace std;

#include <boost/operators.hpp>
using namespace boost;

//
class demo_class
{
    
    
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;

	assert(a < b);
	assert(b >= a);


}

//

class point : //boost::less_than_comparable<point,
			  //boost::equality_comparable<point> >
	totally_ordered<point,
	additive<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);
	}

	friend bool operator==(const point& l, const point& r)
	{
    
    
		return r.x == l.x && r.y == l.y && r.z == l.z;
	}

	point& operator+=(const point& r)       //支持addable概念
	{
    
    
		x += r.x;
		y += r.y;
		z += r.z;
		return *this;
	}

	point& operator-=(const point& r)   //支持subtractable概念
	{
    
    
		x -= r.x;
		y -= r.y;
		z -= r.z;
		return *this;
	}

};

void case2()
{
    
    
	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));

	{
    
    
		point p0, p1(1, 2, 3), p2(p1), p3(3, 2, 1);
		assert(p1 == p2);
		assert(p1 != p3);
	}
}

//
#include <boost/assign.hpp>
void case3()
{
    
    
	point p0, p1(1, 2, 3), p2(5, 6, 7), p3(3, 2, 1);

	using namespace boost::assign;
	vector<point> v = (list_of(p0), p1, p2, p3);

	auto pos = std::find(v.begin(), v.end(), point(1, 2, 3));
	pos->print();

	(p1 + p2).print();
	(p3 - p1).print();

	assert((p2 - p2) == p0);

}

//
void case4()
{
    
    
	point p0, p1(1, 2, 3), p2(5, 6, 7), p3(3, 2, 1);

	using namespace boost::assign;
	vector<point> v = (list_of(p0), p1, p2, p3);

	auto pos = std::find(v.begin(), v.end(), point(1, 2, 3));
	for (; pos != v.end();
		pos = std::find(pos + 1, v.end(), point(1, 2, 3)))
	{
    
    
		pos->print();
	}

	pos = std::find(v.begin(), v.end(), point(2, 1, 3));
	assert(pos == v.end());

}

//
template<typename T>
class my_smart_ptr :
	public dereferenceable<my_smart_ptr<T>, T* >
{
    
    
	T* p;
public:
	my_smart_ptr(T* x) :p(x) {
    
    }
	~my_smart_ptr() {
    
     delete p; }
	T& operator*() const
	{
    
    
		return *p;
	}
};

void case5()
{
    
    
	my_smart_ptr<string > p(new string("123"));
	assert(p->size() == 3);
}

//
template<typename T>
class my_smart_array :
	public indexable<my_smart_array<T>, int, T& >
{
    
    
	T* p;
public:
	typedef my_smart_array<T> this_type;
	typedef T* iter_type;

	my_smart_array(T* x) :p(x) {
    
    }
	~my_smart_array() {
    
     delete[] p; }

	friend iter_type operator+(const this_type& a, int n)
	{
    
    
		return a.p + n;
	}
};

void case6()
{
    
    
	my_smart_array<double> ma(new double[10]);
	ma[0] = 1.0;
	*(ma + 1) = 2.0;
	cout << ma[1] << endl;

}

//
#include <boost/core/explicit_operator_bool.hpp>

struct demo_a
{
    
    
	BOOST_EXPLICIT_OPERATOR_BOOL()
		bool operator!() const
	{
    
    
		return false;
	}
	//explicit operator bool() const {
    
    
	//    return true;
	//}
};

struct demo_b
{
    
    
	BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT()
		bool operator!() const
	{
    
    
		return true;
	}
	//explicit operator bool() const {
    
    
	//    return false;
	//}
};

void case7()
{
    
    
	demo_a a;
	demo_b b;
	assert(a && !b);
	//assert(a != b);
	//assert(a > b);
}

//

int main()
{
    
    
	case1();
	case2();
	case3();
	case4();
	case5();
	case6();
	case7();
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_36314864/article/details/132428011