本节书摘来自异步社区出版社《21天学通C++(第7版)》一书中的第12章,第12.4节,作者: 【美】Siddhartha Rao, 【德】Nicolai M. Josuttis,更多章节内容可以访问云栖社区“异步社区”公众号查看。
12.4 函数运算符operator()
21天学通C++(第7版)
operator()让对象像函数,被称为函数运算符。函数运算符用于标准模板库(STL)中,通常是STL算法中。其用途包括决策。根据使用的操作数数量,这样的函数对象通常称为单目谓词或双目谓词。下面分析一个非简单的函数对象,如程序清单12.11所示,以便理解使用如此有意思的名称的原因!
程序清单12.11 一个使用operator()实现的函数对象
输出:
分析:
第8~11行实现了operator(),然后在main()函数的第18行使用了它。注意,之所以能够在第18行将对象mDisplayFuncObject用作函数,是因为编译器隐式地将它转换为对函数operator()的调用。
因此,这个运算符也称为operator()函数,对象CDisplay也称为函数对象或functor。第21章将详尽地讨论这个主题。
C++11
用于高性能编程的移动构造函数和移动赋值运算符
移动构造函数和移动赋值运算符乃性能优化功能,属于C++11标准的一部分,旨在避免复制不必要的临时值(当前语句执行完毕后就不再存在的右值)。对于那些管理动态分配资源的类,如动态数组类或字符串类,这很有用。
1.不必要的复制带来的问题
请看程序清单12.5实现的加法运算符,注意到它创建并返回一个拷贝;减法运算符亦如此。使用下面的语法创建新的MyString实例时,情况将如何呢?
这种方式非常直观,它使用双目加法运算符(+)将三个字符串拼接起来。该运算符的实现类似于下面这样:
这个加法运算符(+)让您能够使用直观的表达式轻松地拼接字符串,但也可能导致性能问题。创建sayHello时,需要执行加法运算符两次,而每次都将创建一个按值返回的临时拷贝,导致执行复制构造函数。复制构造函数执行深复制,而生成的临时拷贝在该表达式执行完毕后就不再存在。总之,该表达式导致生成一些临时拷贝(准确地说是右值),而它们在当前语句执行完毕后就不再需要。这一直是C++带来的性能瓶颈,直到最近才得以解决。
C++11解决了这个问题:编译器意识到需要创建临时拷贝时,将转而使用移动构造函数和移动赋值运算符——如果您提供了它们。
2.声明移动构造函数和移动赋值运算符
移动构造函数的声明语法如下:
从上述代码可知,相比于常规赋值构造函数和复制赋值运算符的声明,移动构造函数和移动赋值运算符的不同之处在于,输入参数的类型为MyClass&&。另外,由于输入参数是要移动的源对象,因此不能使用const进行限定,因为它将被修改。返回类型没有变,因为它们分别是构造函数和赋值运算符的重载版本。
在需要创建临时右值时,遵循C++的编译器将使用移动构造函数(而不是复制构造函数)和移动赋值运算符(而不是复制赋值运算符)。移动构造函数和移动赋值运算符的实现中,只是将资源从源移到目的地,而没有进行复制。程序清单12.12演示了如何使用这两项C++11新增功能对MyString类进行优化。
程序清单12.12 除复制构造函数和复制赋值运算符外,还包含移动构造函数和移动赋值运算符的MyString类
输出:
没有移动构造函数和移动赋值构造函数(将第95~119行注释掉)时的输出:
添加移动构造函数和移动赋值构造函数后的输出:
分析:
这个代码示例很长,但大部分都在本书前面介绍过。在该程序清单中,最重要的部分是第95~119行,其中实现了移动构造函数和移动赋值运算符。这些C++11新增功能生成的输出使用粗体表示。注意到相比于没有这两个实体时,输出变化很大。如果您查看移动构造函数和移动赋值运算符的实现,将发现移动语义基本上是通过接管移动源中资源的所有权实现的,如移动构造函数的第101行和移动赋值运算符的第114行所示。接下来,将移动源指针设置为NULL,如第102和115行所示。这样,移动源被销毁时,通过析构函数(第16~20行)调用的delete什么也不会做,因为所有权已转交给目标对象。注意到在没有移动构造函数时,将调用复制构造函数,它对指向的字符串进行深复制。总之,移动构造函数避免了不必要的内存分配和复制步骤,从而节省了大量的处理时间。
移动构造函数和移动赋值运算符是可选的。不同于复制构造函数和复制赋值运算符,如果您没有提供移动构造函数和移动赋值运算符,编译器并不会添加默认实现。
对于管理动态分配资源的类,可使用C++11新增的这项功能对其进行优化,避免在只需临时拷贝的情况下进行深复制。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。