Team LiB
Previous Section Next Section

14.1. Defining an Overloaded Operator

14.1. 重载操作符的定义

Overloaded operators are functions with special names: the keyword operator followed by the symbol for the operator being defined. Like any other function, an overloaded operator has a return type and a parameter list.

重载操作符是具有特殊名称的函数:保留字 operator 后接需定义的操作符号。像任意其他函数一样,重载操作符具有返回类型和形参表,如下语句:

     Sales_item operator+(const Sales_item&, const Sales_item&);

declares the addition operator that can be used to "add" two Sales_item objects and yields a copy of a Sales_item object.

声明了加号操作符,可用于将两个 Sales_item 对象“相加”并获得一个 Sales_item 对象的副本。

With the exception of the function-call operator, an overloaded operator has the same number of parameters (including the implicit this pointer for member functions) as the operator has operands. The function-call operator takes any number of operands.

除了函数调用操作符之外,重载操作符的形参数目(包括成员函数的隐式 this 指针)与操作符的操作数数目相同。函数调用操作符可以接受任意数目的操作数。

Overloaded Operator Names

重载的操作名

Table 14.1 on the next page lists the operators that may be overloaded. Those that may not be overloaded are listed in Table 14.2.

表 14.1 列出了可以重载的操作符,不能重载的在表 14.2 列出。

Table 14.1. Overloadable Operators
表 14.1. 可重载的操作符

+

-

*

/

%

^

&

|

~

!

,

=

<

>

<=

>=

++

--

<<

>>

==

!=

&&

||

+=

-=

/=

%=

^=

&=

|=

*=

<<=

>>=

[]

()

->

->*

new

new []

delete

delete []


Table 14.2. Operators That Cannot Be Overloaded
表 14.2. 不能重载的操作符

::

.*

.

?:


New operators may not be created by concatenating other legal symbols. For example, it would be illegal to attempt to define an operator** to provide exponentiation. Overloading new and delete is described in Chapter 18 (p. 753).

通过连接其他合法符号可以创建新的操作符。例如,定义一个 operator** 以提供求幂运算是合法的。第十八章将介绍重载 newdelete

Overloaded Operators Must Have an Operand of Class Type

重载操作符必须具有一个类类型操作数

The meaning of an operator for the built-in types may not be changed. For example, the built-in integer addition operation cannot be redefined:

用于内置类型的操作符,其含义不能改变。例如,内置的整型加号操作符不能重定义:

     // error: cannot redefine built-in operator for ints
     int operator+(int, int);

Nor may additional operators be defined for the built-in data types. For example, an operator+ taking two operands of array types cannot be defined.

也不能为内置数据类型重定义加号操作符。例如,不能定义接受两个数组类型操作数的 operator+

An overloaded operator must have at least one operand of class or enumeration (Section 2.7, p. 62) type. This rule enforces the requirement that an overloaded operator may not redefine the meaning of the operators when applied to objects of built-in type.

重载操作符必须具有至少一个类类型或枚举类型(第 2.7 节)的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。



Precedence and Associativity Are Fixed

优先级和结合性是固定的

The precedence (Section 5.10.1, p. 168), associativity, or number of operands of an operator cannot be changed. Regardless of the type of the operands and regardless of the definition of what the operations do, this expression

操作符的优先级(第 5.10.1 节)、结合性或操作数目不能改变。不管操作数的类型和操作符的功能定义如何,表达式

     x == y +z;

always binds the arguments y and z to operator+ and uses that result as the right-hand operand to operator==.

总是将实参 yz 绑定到 operator+,并且将结果用作 operator== 右操作数。

Four symbols (+, -, *, and &) serve as both unary and binary operators. Either or both of these operators can be overloaded. Which operator is being defined is controlled by the number of operands. Default arguments for overloaded operators are illegal, except for operator(), the function-call operator.

有四个符号(+, -, *&)既可作一元操作符又可作二元操作符,这些操作符有的在其中一种情况下可以重载,有的两种都可以,定义的是哪个操作符由操作数数目控制。除了函数调用操作符 operator() 之外,重载操作符时使用默认实参是非法的。

Short-Ciruit Evaluation Is Not Preserved

不再具备短路求值特性

Overloaded operators make no guarantees about the order in which operands are evaluated. In particular, the operand-evaluation guarantees of the built-in logical AND, logical OR (Section 5.2, p. 152), and comma (Section 5.9, p. 168) operators are not preserved. Both operands to an overloaded version of && or || are always evaluated. The order in which those operands are evaluated is not stipulated. The order in which the operands to the comma are evaluated is also not defined. For this reason, it is usually a bad idea to overload &&, ||, or the comma operator.

重载操作符并不保证操作数的求值顺序,尤其是,不会保证内置逻辑 AND、逻辑 OR(第 5.2 节)和逗号操作符(第 5.9 节)的操作数求值。在 &&|| 的重载版本中,两个操作数都要进行求值,而且对操作数的求值顺序不做规定。因此,重载 &&、|| 或逗号操作符不是一种好的做法。

Class Member versus Nonmember

类成员与非成员

Most overloaded operators may be defined as ordinary nonmember functions or as class member functions.

大多数重载操作符可以定义为普通非成员函数或类的成员函数。

Overloaded functions that are members of a class may appear to have one less parameter than the number of operands. Operators that are member functions have an implicit this parameter that is bound to the first operand.

作为类成员的重载函数,其形参看起来比操作数数目少 1。作为成员函数的操作符有一个隐含的 this 形参,限定为第一个操作数。



An overloaded unary operator has no (explicit) parameter if it is a member function and one parameter if it is a nonmember function. Similarly, an overloaded binary operator would have one parameter when defined as a member and two parameters when defined as a nonmember function.

重载一元操作符如果作为成员函数就没有(显式)形参,如果作为非成员函数就有一个形参。类似地,重载二元操作符定义为成员时有一个形参,定义为非成员函数时有两个形参。

The Sales_item class offers a good example of member and nonmember binary operators. We know that the class has an addition operator. Because it has an addition operator, we ought to define a compound-assignment (+=) operator as well. This operator will add the value of one Sales_item object into another.

Sales_item 中给出了成员和非成员二元操作符的良好例子。我们知道该类有一个加号操作符。因为它有一个加号操作符,所以也应该定义一个复合赋值(+=)操作符,该操作符将一个 Sales_item 对象的值加至另一个 Sales_item 对象。

Ordinarily we define the arithmetic and relational operators as nonmember functions and we define assignment operators as members:

一般将算术和关系操作符定义非成员函数,而将赋值操作符定义为成员:

     // member binary operator: left-hand operand bound to implicit this pointer
     Sales_item& Sales_item::operator+=(const Sales_item&);
     // nonmember binary operator: must declare a parameter for each operand
     Sales_item operator+(const Sales_item&, const Sales_item&);

Both addition and compound assignment are binary operators, yet these functions define a different number of parameters. The reason for the discrepancy is the this pointer.

加和复合赋值都是二元操作符,但这些函数定义了不同数目的形参,差异的原因在于 this 指针。

When an operator is a member function, this points to the left-hand operand. Thus, the nonmember operator+ defines two parameters, both references to const Sales_item objects. Even though compound assignment is a binary operator, the member compound-assignment operator takes only one (explicit) parameter. When the operator is used, a pointer to the left-hand operand is automatically bound to this and the right-hand operand is bound to the function's sole parameter.

当操作符为成员函数,this 指向左操作数,因此,非成员 operator+ 定义两个形参,都引用 const Sales_item 对象。即使复合赋值是二元操作符,成员复合赋值操作符也只接受一个(显式的)形参。使用操作符时,一个指向左操作数的指针自动绑定到 this,而右操作符限定为函数的唯一形参。

It is also worth noting that compound assignment returns a reference and the addition operator returns a Sales_item object. This difference matches the return types of these operators when applied to arithmetic types: Addition yields an rvalue and compound assignment returns a reference to the left-hand operand.

复合赋值返回一个引用而加操作符返回一个 Sales_item 对象,这也没什么。当应用于算术类型时,这一区别与操作符的返回类型相匹配:加返回一个右值,而复合赋值返回对左操作数的引用。

Operator Overloading and Friendship

操作符重载和友元关系

When operators are defined as nonmember functions, they often must be made friends (Section 12.5, p. 465) of the class(es) on which they operate. We'll see later in this chapter two reasons why operators might be defined as nonmembers. In such cases, the operator often needs access to the private parts of the class.

操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元12.5 节)。在本章的后面部分,将给出操作符可以定义为非成员的两个原因。在这种情况下,操作符通常需要访问类的私有部分。

Our Sales_item class is again a good example of why some operators need to be friends. It defines one member operator and has three nonmember operators. Those nonmember operators, which need access to the private data members, are declared as friends:

Sales_item 类也是说明为何有些操作符需要设置为友元的一个好例子。它定义了一个成员操作符,并且有三个非成员操作符。这些非成员操作符需要访问私有数据成员,声明为友元:

     class Sales_item {
         friend std::istream& operator>>
                       (std::istream&, Sales_item&);
         friend std::ostream& operator<<
                       (std::ostream&, const Sales_item&);
     public:
         Sales_item& operator+=(const Sales_item&);
     };
         Sales_item operator+(const Sales_item&, const Sales_item&);

That the input and output operators need access to the private data should not be surprising. After all, they read and write those members. On the other hand, there is no need to make the addition operator a friend. It can be implemented using the public member operator+=.

输入和输出操作符需要访问 private 数据不会令人惊讶,毕竟,它们的作用是读入和写出那些成员。另一方面,不需要将加操作符设置为友元,它可以用 public 成员 operator+= 实现。

Using Overloaded Operators

使用重载操作符

We can use an overloaded operator in the same way that we'd use the operator on operands of built-in type. Assuming item1 and item2 are Sales_item objects, we might print their sum in the same way that we'd print the sum of two ints:

使用重载操作符的方式,与内置类型操作数上使用操作符的方式一样。假定 item1item2Sales_item 对象,可以打印它们的和,就像打印两个 int 的和一样:

     cout << item1 + item2 << endl;

This expression implicitly calls the operator+ that we defined for Sales_items.

这个表达式隐式调用为 Sales_items 类而定义的 operator+

We also can call an overloaded operator function in the same way that we call an ordinary function: We name the function and pass an appropriate number of arguments of the appropriate type:

也可以像调用普通函数一样调用重载操作符函数,指定函数并传递适当类型适当数目的形参:

     // equivalent direct call to nonmember operator function
     cout << operator+(item1, item2) << endl;

This call has the same effect as the expression that added item1 and item2.

这个调用与 item1item2 相加的表达式等效。

We call a member operator function the same way we call any other member function: We name an object on which to run the function and then use the dot or arrow operator to fetch the function we wish to call passing the required number and type of arguments. In the case of a binary member operator function, we must pass a single operand:

调用成员操作符函数与调用任意其他函数是一样的:指定运行函数的对象,然后使用点或箭头操作符获取希望调用的函数,同时传递所需数目和类型的实参。对于二元成员操作符函数的情况,我们必须传递一个操作数:

     item1 += item2;            // expression based "call"
     item1.operator+=(item2);   // equivalent call to member operator function

Each of these statements adds the value of item2 into item1. In the first case, we implicitly call the overloaded operator function using expression syntax. In the second, we call the member operator function on the object item1.

两个语句都将 item2 的值加至 item1。第一种情况下,使用表达式语法隐式调用重载操作符函数:第二种情况下,在 item1 对象上调用成员操作符函数。

Exercises Section 14.1

习题 14.1

Exercise 14.1:

In what ways does an overloaded operator differ from a built-in operator? In what ways are overloaded operators the same as the built-in operators?

在什么情况下重载操作符与内置操作符不同?在什么情况下重载操作符与内置操作符相同?

Exercise 14.2:

Write declarations for the overloaded input, output, addition and compound-assignment operators for Sales_item.

Sales_item 编写输入、输出、加以及复合赋值操作符的重载声明。

Exercise 14.3:

Explain the following program, assuming that the Sales_item constructor that takes a string is not explicit. Explain what happens if that constructor is explicit.

解释如下程序,假定 Sales_item 构造函数的参数是一个 string,且不为 explicit。解释如果构造函数 explicit 怎样。

     string null_book = "9-999-99999-9";
     Sales_item item(cin);
     item += null_book;

Exercise 14.4:

Both the string and vector types define an overloaded == that can be used to compare objects of those types. Identify which version of == is applied in each of the following expressions:

stringvector 类都定义了一个重载的 ==,可用于比较这些类的对象。指出下面表达式中应用了哪个 == 版本:

     string s; vector<string> svec1, svec2;
     "cobble" == "stone"
     svec1[0] == svec2[0];
     svec1 == svec2


14.1.1. Overloaded Operator Design

14.1.1. 重载操作符的设计

When designing a class there are some useful rules of thumb to keep in mind when deciding which, if any, overloaded operators to provide.

设计类的时候,需要记住一些有用的经验原则,可以有助于确定应该提供哪些重载操作符(如果需要提供)。

Don't Overload Operators with Built-in Meanings
不要重载具有内置含义的操作符

The assignment, address of, and comma operators have default meanings for operands of class types. If there is no overloaded version specified, the compiler defines its own version of these operators:

赋值操作符、取地址操作符和逗号操作符对类类型操作数有默认含义。如果没有特定重载版本,编译器就自己定义以下这些操作符。

  • The synthesized assignment operator (Section 13.2, p. 482) does memberwise assignment: It uses each member's own assignment operator to assign each member in turn.

    合成赋值操作符(第 13.2 节)进行逐个成员赋值:使用成员自己的赋值:使用成员自己的赋值操作依次对每个成员进行赋值。

  • By default the address of (&) and comma (,) operators execute on class type objects the same way they do on objects of built-in type. The address of operator returns the address in memory of the object to which it is applied. The comma operator evaluates each expression from left to right and returns the value of its rightmost operand.

    默认情况下,取地址操作符(&)和逗号操作符(,)在类类型对象上的执行,与在内置类型对象上的执行一样。取地址操作符返回对象的内存地址,逗号操作符从左至右计算每个表达式的值,并返回最右边操作数的值。

  • The built-in logical AND (&&) and OR(||) operators apply short-circuit evaluation (Section 5.2, p. 152). If the operator is redefined, the short-circuit nature of the operators is lost.

    内置逻辑与(&&)和逻辑或(||)操作符使用短路求值(第 5.2 节)。如果重新定义该操作符,将失去操作符的短路求值特征。

The meaning of these operators can be changed by redefining them for operands of a given class type.

通过为给定类类型的操作数重定义操作符,可以改变这些操作符的含义。

It is usually not a good idea to overload the comma, address-of, logical AND, or logical OR operators. These operators have built-in meanings that are useful and become inaccessible if we define our own versions.

重载逗号、取地址、逻辑与、逻辑或等等操作符通常不是好做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义。



We sometimes must define our own version of assignment. When we do so, it should behave analogously to the synthesized operators: After an assignment, the values in the left-hand and right-hand operands should be the same and the operator should return a reference to its left-hand operand. Overloaded assignment should customize the built-in meaning of assignment, not circumvent it.

有时我们需要定义自己的赋值运算。这样做时,它应表现得类似于合成操作符:赋值之后,左右操作数的值应是相同的,并且操作符应返回对左操作数的引用。重载的赋值运算应在赋值的内置含义基础上进行定制,而不是完全绕开。

Most Operators Have No Meaning for Class Objects
大多数操作符对类对象没有意义

Operators other than assignment, address-of, and comma have no meaning when applied to an operand of class type unless an overloaded definition is provided. When designing a class, we decide which, if any, operators to support.

除非提供了重载定义,赋值、取地址和逗号操作符对于类类型操作数没有意义。设计类的时候,应该确定要支持哪些操作符。

The best way to design operators for a class is first to design the class' public interface. Once the interface is defined, it is possible to think about which operations should be defined as overloaded operators. Those operations with a logical mapping to an operator are good candidates. For example,

为类设计操作符,最好的方式是首先设计类的公用接口。定义了接口之后,就可以考虑应将哪些操作符定义为重载操作符。那些逻辑上可以映射到某个操作符的操作可以考虑作为候选的重载操作符。例如:

  • An operation to test for equality should use operator==.

    相等测试操作应使用 operator==

  • Input and output are normally done by overloading the shift operators.

    一般通过重载移位操作符进行输入和输出。

  • An operation to test whether the object is empty could be represented by the logical NOT operator, operator!.

    测试对象是否为空的操作可用逻辑非操作符 operator! 表示。

Compound Assignment Operators
复合赋值操作符

If a class has an arithmetic (Section 5.1, p. 149) or bitwise (Section 5.3, p. 154) operator, then it is usually a good idea to provide the corresponding compound-assignment operator as well. For example, our Sales_item class defined the + operator. Logically, it also should define +=. Needless to say, the += operator should be defined to behave the same way the built-in operators do: Compound assignment should behave as + followed by =.

如果一个类有算术操作符(第 5.1 节)或位操作符(第 5.3 节),那么,提供相应的复合赋值操作符一般是个好的做法。例如,Sales_item 类定义了 + 操作符,逻辑上,它也应该定义 +=。不用说,操作符的行为应定义为与内置操作符一样:复合赋值的行为应与 + 之后接着 = 类似。

Equality and Relational Operators
相等和关系操作符

Classes that will be used as the key type of an associative container should define the < operator. The associative containers by default use the < operator of the key type. Even if the type will be stored only in a sequential container, the class ordinarily should define the equality (==) and less-than (<) operators. The reason is that many algorithms assume that these operators exist. As an example, the sort algorithm uses < and find uses ==.

将要用作关联容器键类型的类应定义 < 操作符。关联容器默认使用键类型的 < 操作符。即使该类型将只存储在顺序容器中,类通常也应该定义相等(==)和小于(<)操作符,理由是许多算法假定这个操作符存在。例如 sort 算法使用 < 操作符,而 find 算法使用 == 操作符。

Caution: Use Operator Overloading Judiciously

警告:审慎使用操作符重载

Each operator has an associated meaning from its use on the built-in types. Binary +, for example, is strongly identified with addition. Mapping binary + to an analogous operation for a class type can provide a convenient notational shorthand. For example, the library string type, following a convention common to many programming languages, uses + to represent concatenation"adding" one string to the other.

每个操作符用于内置类型都有关联的含义。例如,二元 + 与加法是完全相同的。将二元 + 对应到一个类类型的类似操作可提供方便的简写方法。例如,标准库的类型 string,遵循许多程序设计语言的通用规范,使用 + 表示连接——将一个串“加”至另一个串。

Operator overloading is most useful when there is a logical mapping of a built-in operator to an operation on our type. Using overloaded operators rather than inventing named operations can make our programs more natural and intuitive. Overuse or outright abuse of operator overloading can make our classes incomprehensible.

当内置操作符和类型上的操作存在逻辑对应关系时,操作符重载最有用。使用重载操作符而不是创造命名操作,可以令程序更自然、更直观,而滥用操作符重载使得我们的类难以理解。

Obvious abuses of operator overloading rarely happen in practice. As an example, no responsible programmer would define operator+ to perform subtraction. More common, but still inadvisable, are uses that contort an operator's "normal" meaning to force a fit to a given type. Operators should be used only for operations that are likely to be unambiguous to users. An operator with ambiguous meaning, in this sense, is one that supports equally well a number of different interpretations.

在实践中很少发生明显的操作符重载滥用。例如,不负责任的程序员可能会定义 operator+ 来执行减法。更常见但仍不可取的是,改变操作符的“正常”含义以强行适应给定类型。操作符应该只用于对用户而言无二义的操作。在这里所谓有二义的操作符,就是指具有多个不同解释的操作符。

When the meaning of an overloaded operator is not obvious, it is better to give the operation a name. It is also usually better to use a named function rather than an operator for operations that are rarely done. If the operation is unusual, the brevity of using an operator is unnecessary.

当一个重载操作符的含义不明显时,给操作取一个名字更好。对于很少用的操作,使用命名函数通常也比用操作符更好。如果不是普通操作,没有必要为简洁而使用操作符。




If the class defines the equality operator, it should also define !=. Users of the class will assume that if they can compare for equality, they can also compare for inequality. The same argument applies to the other relational operators as well. If the class defines <, then it probably should define all four relational operators (>, >=, <, and <=).

如果类定义了相等操作符,它也应该定义不等操作符 !=。类用户会假设如果可以进行相等比较,则也可以进行不等比较。同样的规则也应用于其他关系操作符。如果类定义了 <,则它可能应该定义全部的四个关系操作符(>,>=,<,<=)。

Choosing Member or Nonmember Implementation
选择成员或非成员实现

When designing the overloaded operators for a class, we must choose whether to make each operator a class member or an ordinary nonmember function. In some cases, the programmer has no choice; the operator must be a member. In other cases, there are some rules of thumb that can help guide the decision. The following guidelines can be of help when deciding whether to make an operator a member or an ordinary nonmember function:

为类设计重载操作符的时候,必须选择是将操作符设置为类成员还是普通非成员函数。在某些情况下,程序员没有选择,操作符必须是成员;在另一些情况下,有些经验原则可指导我们做出决定。下面是一些指导原则,有助于决定将操作符设置为类成员还是普通非成员函数:

  • The assignment (=), subscript ([]), call (()), and member access arrow (->) operators must be defined as members. Defining any of these operators as a nonmember function is flagged at compile time as an error.

    赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。

  • Like assignment, the compound-assignment operators ordinarily ought to be members of the class. Unlike assignment, they are not required to be so and the compiler will not complain if a nonmember compound-assignment operator is defined.

    像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。

  • Other operators that change the state of their object or that are closely tied to their given typesuch as increment, decrement, and dereferenceusually should be members of the class.

    改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员。

  • Symmetric operators, such as the arithmetic, equality, relational, and bitwise operators, are best defined as ordinary nonmember functions.

    对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

Exercises Section 14.1.1

Exercise 14.5:

List the operators that must be members of a class.

列出必须定义为在类成员的操作符。

Exercise 14.6:

Explain why and whether each of the following operators should be class members:

解释下面操作符是否应该为类成员,为什么?

     (a) + (b) += (c) ++ (d) -> (e) << (f) && (g) == (h) ()


Team LiB
Previous Section Next Section