《C++编程惯用法——高级程序员常用方法和技巧》——2.5 操作符重载:成员或非成员?

简介:

本节书摘来自异步社区出版社《C++编程惯用法——高级程序员常用方法和技巧》一书中的第2章,第2.5节,作者: 【美】Robert B. Murray ,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.5 操作符重载:成员或非成员?

在C++中,对于操作符的重载有着两种方法:将该操作符作为成员函数或者不作为成员函数。在本节中,我们将会对如何为一个特定的操作符选择合适的重载方式给出一些评判标准。

显而易见的是,如果我们正在为一个不属于我们的类重载一个操作符,这个操作符应该是一个非成员函数:如果类不属于我们,我们也就不能向其中增加成员了!在本节的剩余部分中假定了我们可以决定是否将操作符定义为一个成员函数。

对于成员和非成员的选择结果会影响到使用该操作符的代码,例如,作为成员函数的操作符可以使用this指针,并且也可以用非限定形式来使用类中的成员。此外,这个选择结果还会影响到用户眼中该操作符的行为。如果操作符被实现为一个成员函数,那么我们就无法对第一个(最左边的)操作符进行隐式类型转换。这种不同存在于用户眼中,但对于编码来说则并不明显,因此它也应该成为我们在成员和非成员之间进行选择的一个主要评判标准。

2.5.1 一元操作符

我们先来看一个有关一元操作符的例子。假设我们有一个类Vector(向量)[3],它创建自一个Direction(方向)对象和一个用double描述的数值:

class Direction {
//此处忽略细节
};

class Vector {
public:
   Vector(const Direction&, double magnitude = 0.0);
};

由于Vector的构造函数中第二个参数有缺省值,我们就可以从一个Direction对象经过隐式转换得到一个Vector对象。

现在我们希望为Vector重载一个一元操作符。如果我们把它定义为非成员函数,那么我们也就可以将它作用到Direction上去(因为Direction可以隐式地转换为Vector):

Vector operator-(const Vector&);
main () {
   Direction d;
   Vector V=-d; //合法的:operator-(Vector(d))
}

如果operator-是一个成员函数,那么隐式转换就将被禁止:

class Vector {
public:
   Vector (const Direction&, double magnitude = 0.0);
   Vector operator-()const;
};

main () {
   Direction d;
   Vector v = -d; //编译期错误
}

我个人认为禁止隐式转换是一件好事,如同我们在2.4节中所看到的,使用隐式转换会使得代码难以被维护和被理解。出于这个原因,我建议:在可能的情况下,最好将一元操作符实现为成员函数。

2.5.2 =,[ ],()以及->

按照C++语言定义来说,这四种操作符必须被实现为成员函数。任何将它们中的某个实现为非成员的尝试都会造成一个编译期的错误:

extern int operator=(Complex&,const Complex&);

//编译期错误:operator=必须是成员函数

2.5.3 其他的二元操作符

对于其他的二元操作符来说,是否将它们实现为成员函数取决于我们是否需要对左操作数进行隐式转换。对于赋值类操作符(如+ =)来说,我们希望的是禁止对左操作数进行隐式转换:

Complex c;
C+=5;

我们很难想象c在+=的操作实施前就被隐式转换成为其他的东西。如果上面的代码顺利地通过了编译,在它运行后c的值将不会得到改变,这一点肯定会让人迷惑不解。如果我们将赋值类操作符定义为成员函数的话,我们就可以禁止这种情况的出现,并且还可以保证它们和operator =(它必须是一个成员函数)具有一致的行为。

此外,对于那些非赋值类从操作符来说,禁止对左操作数进行隐式转换但却允许对右操作数进行隐式转换同样会让人困惑。例如:

class Complex {
//此处忽略细节
public:
   Complex(double = 0.0, double = 0.0);
   Compiex operator+(const Complex&)const;
};

main () {
   Compiex c(1.0):
   Complex d = c + 1.0;  //OK
   Complex e = 1.0 + c;  //编译期错误
}

注意:我们在Complex的构造函数中使用了有缺省值的参数。这使得我们可以从double通过隐式转换得到Complex(这时,构造函数的第二个参数将会是缺省值0.0)。上面代码中对于operator+的第二次调用出错的原因在于:在当前作用域中,没有一个合适的声明对应于这次函数调用;编译器不会对成员函数的左操作数进行隐式转换。如果我们将operator+声明为非成员函数,那么上面两种形式的调用都将顺利地通过编译。

当两个操作数都是操作的输入,并且没有一个会受到操作的影响,那么我们最好将该操作符定义为一个非成员函数。这可以确保在有隐式转换的条件下,这两个操作数都可以以同样的方式来进行操作。

图2.1中归纳了本节中对于操作符是否该定义为成员函数的建议:
image

相关文章
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
1378 94
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
1278 163
|
10月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
9月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
515 6
|
10月前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
10月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
10月前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
342 5
|
消息中间件 存储 安全
|
IDE Java 程序员
C++ 程序员的 Java 指南
一个 C++ 程序员自己总结的 Java 学习中应该注意的点。
131 5