Team LiB
Previous Section Next Section

14.9. Conversions and Class Types

14.9. 转换与类类型

In Section 12.4.4 (p. 461) we saw that a nonexplicit constructor that can be called with one argument defines an implicit conversion. The compiler will use that conversion when an object of the argument type is supplied and an object of the class type is needed. Such constructors define conversions to the class type.

第 12.4.4 节介绍过,可用一个实参调用的非 explicit 构造函数定义一个隐式转换。当提供了实参类型的对象而需要一个类类型的对象时,编译器将使用该转换。这种构造函数定义了到类类型的转换。

In addition to defining conversions to a class type, we can also define conversions from the class type. That is, we can define a conversion operator that, given an object of the class type, will generate an object of another type. As with other conversions, the compiler will apply this conversion automatically. Before showing how to define such conversions, we'll look at why they might be useful.

除了定义类类型的转换之外,我们还可以定义类类型的转换。即,我们可以定义转换操作符,给定类类型的对象,该操作符将产生其他类型的对象。像其他转换一样,编译器将自动应用这个转换。在介绍如何定义这种转换之前,将说明它们为什么可能有用。

14.9.1. Why Conversions Are Useful

14.9.1. 转换为什么有用

Assume that we want to define a class, which we'll name SmallInt, to implement safe small integers. Our class will allow us to define objects that could hold the same range of values as an 8-bit unsigned charthat is, 0 to 255. This class would catch under- and overflow errors and so would be safer to use than a built-in unsigned char.

假定想要定义一个名为 SmallInt 的类,该类实现安全小整数,这个类将使我们能够定义对象以保存与 8 位 unsigned char 同样范围的值,即,0 到 255。这个类可以捕获下溢和上溢错误,因此使用起来比内置 unsigned char 更安全。

We'd want our class to define all the same operations as are supported by an unsigned char. In particular, we'd want to define the five arithmetic operators (+, -, *, /, and %) and the corresponding compound-assignment operators; the four relational operators (<, <=, >, and >=); and the equality operators (== and !=). Evidently, we'd need to define 16 operators.

我们希望这个类定义 unsigned char 支持的所有操作。具体而言,我们想定义 5 个算术操作符(+-*/%)及其对应的复合赋值操作符,4 个关系操作符(<<=>>=),以及相等操作符(==!=)。显然,需要定义 16 个操作符。

Supporting Mixed-Type Expressions
支持混合类型表达式

Moreover, we'd like to be able to use these operators in mixed-mode expressions. For example, it should be possible to add two SmallInt objects and also possible to add any of the arithmetic types to a SmallInt. We could come close by defining three instances for each operator:

而且,我们希望可以在混合模式表达式中使用这些操作符。例如,应该可以将两个 SmallInt 对象相加,也可以将任意算术类型加到 SmallInt。通过为每个操作符定义三个实例来达到目标:

     int operator+(int, const SmallInt&);
     int operator+(const SmallInt&, int);
     SmallInt operator+(const SmallInt&, const SmallInt&);

Because there is a conversion to int from any of the arithmetic types, these three functions would cover our desire to support mixed mode use of SmallInt objects. However, this design only approximates the behavior of built-in integer arithmetic. It wouldn't properly handle mixed-mode operations for the floating-point types, nor would it properly support addition of long, unsigned int, or unsigned long. The problem is that this design converts all arithmetic types even those bigger than intto int and does an int addition.

因为存在从任意算术类型到 int 的转换,这三个函数可以涵盖支持 SmallInt 对象的混合模式使用的要求。但是,这个设计仅仅接近内置整数运算的行为,它不能适当处理浮点类型混合模式操作,也不能适当支持 longunsigned intunsigned long 的加运算。问题在于这个设计将所有算术类型(甚至包括那些比 int 大的)转换为 int 并进行 int 加运算。

Conversions Reduce the Number of Needed Operators
转换减少所需操作符的数目

Even ignoring the issue of floating-point or large integral operands, if we implemented this design, we'd have to define 48 operators! Fortunately, C++ provides a mechanism by which a class can define its own conversions that can be applied to objects of its class type. For SmallInt, we could define a conversion from SmallInt to type int. If we define the conversion, then we won't need to define any of the arithmetic, relational, or equality operators. Given a conversion to int, a SmallInt object could be used anywhere an int could be used.

即使忽略浮点或大整型操作数的问题,如果要实现这个设计,也必须定义 48 个操作符!幸好,C++ 提供了一种机制,利用这种机制,一个类可以定义自己的转换,应用于其类类型对象。对 SmallInt 而言,可以定义一个从 SmallIntint 类型的转换。如果定义了该转换,则无须再定义任何算术、关系或相等操作符。给定到 int 的转换,SmallInt 对象可以用在任何可用 int 值的地方。

If there were a conversion to int, then

如果存在一个到 int 的转换,则以下代码:

     SmallInt si(3);
     si + 3.14159;         // convert si to int, then convert to double

would be resolved by

可这样确定:

  1. Converting si to an int.

    si 转换为 int 值。

  2. Converting the resulting int to double and adding it to the double literal constant 3.14159, yielding a double value.

    将所得 int 结果转换为 double 值并与双精度字面值常量 3.14159 相加,得到 double 值。

14.9.2. Conversion Operators

14.9.2. 转换操作符

A conversion operator is a special kind of class member function. It defines a conversion that converts a value of a class type to a value of some other type. A conversion operator is declared in the class body by specifying the keyword operator followed by the type that is the target type of the conversion:

转换操作符是一种特殊的类成员函数。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型:

     class SmallInt {
     public:
         SmallInt(int i = 0): val(i)
         { if (i < 0 || i > 255)
            throw std::out_of_range("Bad SmallInt initializer");
         }
         operator int() const { return val; }
     private:
         std::size_t val;
     };

A conversion function takes the general form

转换函数采用如下通用形式:

     operator type();

where type represents the name of a built-in type, a class type, or a name defined by a typedef. Conversion functions can be defined for any type (other than void) that could be a function return type. In particular, conversions to an array or function type are not permitted. Conversions to pointer typesboth data and function pointersand to reference types are allowed.

这里,type 表示内置类型名、类类型名或由类型别名定义的名字。对任何可作为函数返回类型的类型(除了 void 之外)都可以定义转换函数。一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的。

A conversion function must be a member function. The function may not specify a return type, and the parameter list must be empty.

转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。



All of the following declarations are errors:

下述所有声明都是错误的:

     operator int(SmallInt &);            // error: nonmember

     class SmallInt {
     public:
         int operator int();              // error: return type
         operator int(int = 0);           // error: parameter list
         // ...
     };

Although a conversion function does not specify a return type, each conversion function must explicitly return a value of the named type. For example, operator int returns an int; if we defined an operator Sales_item, it would return a Sales_item; and so on.

虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定类型的值。例如,operator int 返回一个 int 值;如果定义 operator Sales_item,它将返回一个 Sales_item 对象,诸如此类。

Conversion operations ordinarily should not change the object they are converting. As a result, conversion operators usually should be defined as const members.

转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为 const 成员。



Using a Class-Type Conversion
使用类类型转换

Once a conversion exists, the compiler will call it automatically (Section 5.12.1, p. 179) in the same places that a built-in conversion would be used:

只要存在转换,编译器将在可以使用内置转换的地方自动调用它(第 5.12.1 节):

  • In expressions:

    在表达式中:

         SmallInt si;
         double dval;
         si >= dval          // si converted to int and then convert to double
    

  • In conditions:

    在条件中:

         if (si)                // si converted to int and then convert to bool
    

  • When passing arguments to or returning values from a function:

    将实参传给函数或从函数返回值:

         int calc(int);
         SmallInt si;
         int i = calc(si);      // convert si to int and call calc
    

  • As operands to overloaded operators:

    作为重载操作符的操作数:

         // convert si to int then call opeator<< on the int value
         cout << si << endl;
    

  • In an explicit cast:

    在显式类型转换中:

         int ival;
         SmallInt si = 3.541; //
         instruct compiler to cast si to int
         ival = static_cast<int>(si) + 3;
    

Class-Type Conversions and Standard Conversions
类类型转换和标准转换

When using a conversion function, the converted type need not exactly match the needed type. A class-type conversion can be followed by a standard conversion (Section 5.12.3, p. 181) if needed to obtain the desired type. For example, in the comparison between a SmallInt and a double

使用转换函数时,被转换的类型不必与所需要的类型完全匹配。必要时可在类类型转换之后跟上标准转换以获得想要的类型。例如,在一个 SmallInt 对象与一个 double 值的比较中:

     SmallInt si;
     double dval;
     si >= dval // si converted to int and then convert to double

si is first converted from a SmallInt to an int, and then the int value is converted to double.

首先将 siSmallInt 对象转换为 int 值,然后将该 int 值转换为 double 值。

Only One Class-Type Conversion May Be Applied
只能应用一个类类型转换

A class-type conversion may not be followed by another class-type conversion. If more than one class-type conversion is needed then the code is in error.

类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错。



For example, assume we had another class, Integral, that could be converted to SmallInt but that had no conversion to int:

例如,假定有另一个类 Integral,它可以转换为 SmallInt 但不能转换为 int

     // class to hold unsigned integral values
     class Integral {
     public:
         Integral(int i = 0): val(i) { }
         operator SmallInt() const { return val % 256; }
     private:
         std::size_t val;
     };

We could use an Integral where a SmallInt is needed, but not where an int is required:

可以在需要 SmallInt 的地方使用 Integral,但不能在需要 int 的地方使用 Integeral

     int calc(int);
     Integral intVal;
     SmallInt si(intVal);  // ok: convert intVal to SmallInt and copy to si
     int i = calc(si);     // ok: convert si to int and call calc
     int j = calc(intVal); // error: no conversion to int from Integral

When we create si, we use the SmallInt copy constructor. First int_val is converted to a SmallInt by invoking the Integral conversion operator to generate a temporary value of type SmallInt. The (synthesized) SmallInt copy constructor then uses that value to initialize si.

创建 si 时使用 SmallInt 复制构造函数。首先调用 Integral 转换操作符产生一个 SmallInt 类型的临时值,将 int_val 对象转换为 SmallInt。然后(合成的)复制构造函数使用该对象值初始化 si

The first call to calc is also okay: The argument si is automatically converted to int, and the int value is passed to the function.

第一个 calc 调用也是正确的:将实参 si 自动转换为 int,然后将 int 值传给函数。

The second call is an error: There is no direct conversion from Integral to int. To get an int from an Integral would require two class-type conversions: first from Integral to SmallInt and then from SmallInt to int. However, the language allows only one class-type conversion, so the call is in error.

第二个 calc 调用是错误的:没有从 Integralint 的直接转换。从 int 需要两次类类型转换:首先从 IntegralSmallInt,然后从 SmallIntint。但是,语言只允许一次类类型转换,所以该调用出错。

Standard Conversions Can Precede a Class-Type Conversion
标准转换可放在类类型转换之前

When using a constructor to perform an implicit conversion (Section 12.4.4, p. 462), the parameter type of the constructor need not exactly match the type supplied. For example, the following code invokes the constructor SmallInt(int) defined in class SmallInt to convert sobj to the type SmallInt:

使用构造函数执行隐式转换(第 12.4.4 节)的时候,构造函数的形参类型不必与所提供的类型完全匹配。例如,下面的代码调用 SmallInt(int) 类中定义的构造函数(SmallInt(int))将 sobj 转换为 SmallInt 类型:

     void calc(SmallInt);
     short sobj;
     // sobj promoted from short to int
     // that int converted to SmallInt through the SmallInt(int) constructor
     calc(sobj);

If needed, a standard conversion sequence can be applied to an argument before a constructor is called to perform a class-type conversion. To call the function calc(), a standard conversion is applied to convert dobj from type double to type int. The SmallInt(int) constructor is then invoked to convert the result of the conversion to the type SmallInt.

如果需要,在调用构造函数执行类类型转换之前,可将一个标准转换序列应用于实参。为了调用函数 calc(),应用标准转换将 dobjdouble 类型转换为 int 类型,然后调用构造函数 SmallInt(int) 将转换结果转换为 SmallInt 类型。

Exercises Section 14.9.2

Exercise 14.40:

Write operators that could convert a Sales_item to string and to double. What values do you think these operators should return? Do you think these conversions are a good idea? Explain why or why not.

编写可将 Sales_item 对象转换为 string 类型和 double 类型的操作符。你认为这些操作符应返回什么值?你认为定义这些操作符是个好办法吗?解释你的结论。

Exercise 14.41:

Explain the difference between these two conversion operators:

解释这两个转换操作符之间的不同:

     class Integral {
     public:
         const int();
         int() const;
     };

Are either of these conversions too restricted? If so, how might you make the conversion more general?

这两个转换操作符是否太严格了?如果是,怎样使得转换更通用一些?

Exercise 14.42:

Define a conversion operator to bool for the CheckoutRecord class from the exercises in Section 14.2.1 (p. 515).

第 14.2.1 节习题中的 CheckoutRecord 类定义到 bool 的转换操作符。

Exercise 14.43:

Explain what the bool conversion operator does. Is that the only possible meaning for this conversion for the CheckoutRecord type? Explain whether you think this conversion is a good use of a conversion operation.

解释 bool 转换操作符做了什么。这是这个 CheckoutRecord 类型转换唯一可能的含义吗?解释你是否认为这个转换是一种转换操作的良好使用。


14.9.3. Argument Matching and Conversions

14.9.3. 实参匹配和转换

The rest of this chapter covers a somewhat advanced topic. It can be safely skipped on first reading.

本章其余部分讨论比较高级的主题。在第一次阅读时可跳过这些内容。



Class-type conversions can be a boon to implementing and using classes. By defining a conversion to int for SmallInts, we made the class easier to implement and easier to use. The int conversion lets users of SmallInt use all the arithmetic and relational operators on SmallInt objects. Moreover, users can safely write expressions that intermix SmallInts and other arithmetic types. The class implementor's job is made much easier by defining a single conversion operator instead of having to define 48 (or more) overloaded operators.

类类型转换可能是实现和使用类的一个好处。通过为 SmallInt 定义到 int 的转换,能够更容易实现和使用 SmallInt 类。int 转换使 SmallInt 的用户能够对 SmallInt 对象使用所有算术和关系操作符,而且,用户可以安全编写将 SmallInt 和其他算术类型混合使用的表达式。定义一个转换操作符就能代替定义 48 个(或更多)重载操作符,类实现者的工作就简单多了。

Class-type conversions can also be a great source of compile-time errors. Problems arise when there are multiple ways to convert from one type to another. If there are several class-type conversions that could be used, the compiler must figure out which one to use for a given expression. In this section, we look at how class-type conversions are used to match an argument to its corresponding parameter. We look first at how parameters are matched for functions that are not overloaded and then look at overloaded functions.

类类型转换也可能是编译时错误的一大来源。当从一个类型转换到另一类型有多种方式时,问题就出现了。如果有几个类类型转换可以使用,编译器必须决定对给定表达式使用哪一个。在这一节,我们介绍怎样用类类型转换将实参和对应形参相匹配。首先介绍非重载函数的形参匹配,然后介绍重载函数的形参匹配。

Used carefully, class-type conversions can greatly simplify both class and user code. Used too freely, they can lead to mysterious compile-time errors that can be hard to understand and hard to avoid.

如果小心使用,类类型转换可以大大简化类代码和用户代码。如果使用得太过自由,类类型转换会产生令人迷惑的编译时错误,这些错误难以理解而且难以避免。



Argument Matching and Multiple Conversion Operators
实参匹配和多个转换操作符

To illustrate how conversions on values of class type interact with function matching, we'll add two additional conversions to our SmallInt class. We'll add a second constructor that takes a double and also define a second conversion operator to convert SmallInt to double:

为了举例说明类类型值的转换怎样与函数匹配相互作用,我们给 SmallInt 类加上另外两个转换,包括接受一个 double 参数的构造函数和一个将 SmallInt 转换为 double 的转换操作符:

     // unwise class definition:
     // multiple constructors and conversion operators to and from the built-in types
     // can lead to ambiguity problems
     class SmallInt {
     public:
         // conversions to SmallInt from int and double
         SmallInt(int = 0);
         SmallInt(double);
         // Conversions to int or double from SmallInt
         // Usually it is unwise to define conversions to multiple arithmetic types
         operator int() const { return val; }
         operator double() const { return val; }
         // ...
     private:
         std::size_t val;
     };

Ordinarily it is a bad idea to give a class conversions to or from two built-in types. We do so here to illustrate the pitfalls involved.

一般而言,给出一个类与两个内置类型之间的转换是不好的做法,在这里这样做是为了举例说明所包含的缺陷。



Consider the simple case where we call a function that is not overloaded:

考虑最简单的调用非重载函数的情况:

     void compute(int);
     void fp_compute(double);
     void extended_compute(long double);
     SmallInt si;
     compute(si);          // SmallInt::operator int() const
     fp_compute(si);       // SmallInt::operator double() const
     extended_compute(si); // error: ambiguous

Either conversion operator could be used in the call to compute:

任一转换操作符都可用于 compute 调用中:

  1. operator int generates an exact match to the parameter type.

    operator int 产生对形参类型的完全匹配。

  2. operator double followed by the standard conversion from double to int matches the parameter type.

    首先调用 operator double 进行转换,后跟从 doubleint 的标准转换与形参类型匹配。

An exact match is a better conversion than one that requires a standard conversion. Hence, the first conversion sequence is better. The conversion function SmallInt::operator int() is chosen to convert the argument.

完全匹配转换比需要标准转换的其他转换更好,因此,第一个转换序列更好,选择转换函数 SmallInt::operator int() 来转换实参。

Similarly, in the second call, fp_compute could be called using either conversion. However, the conversion to double is an exact match; it requires no additional standard conversion.

类似地,在第二个调用中,可用任一转换调用 fp_compute。但是,到 double 的转换是一个完全匹配,不需要额外的标准转换。

The final call to extended_compute is ambiguous. Either conversion function could be used, but each would have to be followed by a standard conversion to get to long double. Hence, neither conversion is better than the other, so the call is ambiguous.

最后一个对 extended_compute 的调用有二义性。可以使用任一转换函数,但每个都必须跟上一个标准转换来获得 long double,因此,没有一个转换比其他的更好,调用具有二义性。

If two conversion operators could be used in a call, then the rank of the standard conversion (Section 7.8.4, p. 272), if any, following the conversion function is used to select the best match.

如果两个转换操作符都可用在一个调用中,而且在转换函数之后存在标准转换(第 7.8.4 节),则根据该标准转换的类别选择最佳匹配。



Argument Matching and Conversions by Constructors
实参匹配和构造函数转换

Just as there might be two conversion operators, there can also be two constructors that might be applied to convert a value to the target type of a conversion.

正如可能存在两个转换操作符,也可能存在两个构造函数可以用来将一个值转换为目标类型。

Consider the manip function, which takes an argument of type SmallInt:

考虑 manip 函数,它接受一个 SmallInt 类型的实参:

     void manip(const SmallInt &);
     double d; int i; long l;
     manip(d);     // ok: use SmallInt(double) to convert the argument
     manip(i);     // ok: use SmallInt(int) to convert the argument
     manip(l);     // error: ambiguous

In the first call, we could use either of the SmallInt constructors to convert d to a value of type SmallInt. The int constructor requires a standard conversion on d, whereas the double constructor is an exact match. Because an exact match is better than a standard conversion, the constructor SmallInt(double) is used for the conversion.

在第一个调用中,可以用任一构造函数将 d 转换为 SmallInt 类型的值。int 构造函数需要对 d 的标准转换,而 double 构造函数完全匹配。因为完全匹配比标准转换更好,所以用构造函数 SmallInt(double) 进行转换。

In the second call, the reverse is true. The SmallInt(int) constructor provides an exact matchno additional conversion is needed. To call the SmallInt constructor that takes a double would require that i first be converted to double. For this call, the int constructor would be used to convert the argument.

在第二个调用中,情况恰恰相反,构造函数 SmallInt(int) 提供完全匹配——不需要附加的转换,调用接受一个 double 参数的 SmallInt 构造函数需要首先将 i 转换为 double 类型。对于这个调用,用 int 构造函数转换实参。

The third call is ambiguous. Neither constructor is an exact match for long. Each would require that the argument be converted before using the constructor:

第三个调用具有二义性。没有构造函数完全匹配于 long。使用每一个构造函数之前都需要对实参进行转换:

  1. standard conversion (long to double) followed by SmallInt(double)

    标准转换(从 longdouble)后跟 SmallInt(double)

  2. standard conversion (long to int) followed by SmallInt(int)

    标准转换(从 longint)后跟 SmallInt(int)

These conversion sequences are indistinguishable, so the call is ambiguous.

这些转换序列是不能区别的,所以该调用具有二义性。

When two constructor-defined conversions could be used, the rank of the standard conversion, if any, required on the constructor argument is used to select the best match.

当两个构造函数定义的转换都可以使用时,如果存在构造函数实参所需的标准转换,就用该标准转换的类型选择最佳匹配。



Ambiguities When Two Classes Define Conversions
当两个类定义了转换时的二义性

When two classes define conversions to each other, ambiguities are likely:

当两个类定义了相互转换时,很可能存在二义性:

     class Integral;
     class SmallInt {
     public:
         SmallInt(Integral); // convert from Integral to SmallInt
         // ...
      };
     class Integral {
     public:
         operator SmallInt() const; // convert from SmallInt to Integral
         // ...
      };
     void compute(SmallInt);
     Integral int_val;
     compute(int_val);  // error: ambiguous

The argument int_val can be converted to a SmallInt in two different ways. The compiler could use the SmallInt constructor that takes an Integral object or it could use the Integral conversion operation that converts an Integral to a SmallInt. Because these two functions are equally good, the call is in error.

实参 int_val 可以用两种不同方式转换为 SmallInt 对象,编译器可以使用接受 Integral 对象的构造函数,也可以使用将 Integral 对象转换为 SmallInt 对象的 Integral 转换操作。因为这两个函数没有高下之分,所以这个调用会出错。

In this case, we cannot use a cast to resolve the ambiguitythe cast itself could use either the conversion operation or the constructor. Instead, we would need to explicitly call the conversion operator or the constructor:

在这种情况下,不能用显式类型转换来解决二义性——显式类型转换本身既可以使用转换操作又可以使用构造函数,相反,需要显式调用转换操作符或构造函数:

     compute(int_val.operator SmallInt());   // ok: use conversion operator
     compute(SmallInt(int_val));             // ok: use SmallInt constructor

Moreover, conversions that we might think would be ambiguous can be legal for what seem like trivial reasons. For example, our SmallInt class constructor copies its Integral argument. If we change the constructor so that it takes a reference to const Integral

而且,由于某些似乎微不足道的原因,我们认为可能有二义性的转换是合法的。例如,SmallInt 类构造函数复制它的 Integral 实参,如果改变构造函数以接受 const Integral 引用:

     class SmallInt {
     public:
     SmallInt(const Integral&);
     };

our call to compute(int_val) is no longer ambiguous! The reason is that using the SmallInt constructor requires binding a reference to int_val, whereas using class Integral's conversion operator avoids this extra step. This small difference is enough to tip the balance in favor of using the conversion operator.

则对 compute(int_val) 的调用不再有二义性!原因在于使用 SmallInt 构造函数需要将一个引用绑定到 int_val,而使用 Integral 类的转换操作符可以避免这个额外的步骤。这一小小区别足以使我们倾向于使用转换操作符。

The best way to avoid ambiguities or surprises is to avoid writing pairs of classes where each offers an implicit conversion to the other.

避免二义性最好的方法是避免编写互相提供隐式转换的成对的类。



Caution: Avoid Overuse of Conversion Functions

警告:避免转换函数的过度使用

As with using overloaded operators, judicious use of conversion operators can greatly simplify the job of a class designer and make using a class easier. However, there are two potential pitfalls: Defining too many conversion operators can lead to ambiguous code, and some conversions can be confusing rather than helpful.

与使用重载操作符一样,转换操作符的适当使用可以大大简化类设计者的工作并使得类的使用更简单。但是,有两个潜在的缺陷:定义太多转换操作符可能导致二义性代码,一些转换可能利大于弊。

The best way to avoid ambiguities is to ensure that there is at most one way to convert one type to another. The best way to do that is to limit the number of conversion operators. In particular there should be only one conversion to a built-in type.

避免二义性最好的方法是,保证最多只有一种途径将一个类型转换为另一类型。做到这点,最好的办法是限制转换操作符的数目,尤其是,到一种内置类型应该只有一个转换。

Conversion operators can be misleading when they are used where there is no obvious single mapping between the class type and the conversion type. In such cases, providing a conversion function may be confusing to the user of the class.

当转换操作符用于没有明显映射关系的类类型和转换类型之间时,容易引起误解,在这种情况下,提供转换函数可能会令类的使用者迷惑不解。

As an example, if we had a class that represented a Date, we might think it would be a good idea to provide a conversion from Date to int. However, what value should the conversion function return? The function might return the julian date, which is the sequence number of the current date starting from 0 as January 1. But should the year precede the day or follow it? That is, would January 31, 1986 be represented as 1986031 or 311986? Alternatively, the conversion operator might return an int representing the day count since some epoch point. The counter might count days since January 1, 1971 or some other starting point.

例如,如果有一个表示 Date 的类,我们可能会认为提供从 Dateint 的转换是个好主意,但是,这个转换函数应返回什么值?该函数可以返回公历日期,这是表示当前日期的一个顺序数,以 0 表示 1 月 1 日,但年份是否应放在日期之前或之后?即,1986 年 1 月 31 日是否应表示为 1986031 或 311986?作为一种选择,转换操作符可以返回一个表示从某个新纪元点开始计数的天数,计数器可以从 1971 年 1 月 1 是或其他起始点开始计算天数。

The problem is that whatever choice is made, the use of Date objects will be ambiguous because there is no single one-to-one mapping between an object of type Date and a value of type int. In such cases, it is better not to define the conversion operator. Instead, the class ought to define one or more ordinary members to extract the information in these various forms.

问题在于,无论怎样选择,Date 对象的使用将具有二义性,因为没有一个 Date 类型对象与 int 类型值之间的一对一映射。在这种情况下,不定义转换函数更好。相反,这个类应该定义一个或多个普通成员从这些不同形式中抽取信息。


14.9.4. Overload Resolution and Class Arguments

14.9.4. 重载确定和类的实参

As we have just seen, the compiler automatically applies a class conversion operator or constructor when needed to convert an argument to a function. Class conversion operators, therefore, are considered during function resolution. Function overload resolution (Section 7.8.2, p. 269) consists of three steps:

正如我们看到的,在需要转换函数的实参时,编译器自动应用类的转换操作符或构造函数。因此,应该在函数确定期间考虑类转换操作符。函数重载确定(第 7.8.2 节)由三步组成:

1.
Determine the set of candidate functions: These are the functions with the same name as the function being called.
确定候选函数集合:这些是与被调用函数同名的函数。

2.
Select the viable functions: These are the candidate functions for which the number and type of the function's parameters match the arguments in the call. When selecting the viable functions, the compiler also determines which conversion operations, if any, are needed to match each parameter.
选择可行的函数:这些是形参数目和类型与函数调用中的实参相匹配的候选函数。选择可行函数时,如果有转换操作,编译器还要确定需要哪个转换操作来匹配每个形参。

3.
The best match function is selected. To determine the best match, the type conversions needed to convert argument(s) to the type of the corresponding parameter(s) are ranked. For arguments and parameters of class type, the set of possible conversions includes class-type conversions.
选择最佳匹配的函数。为了确定最佳匹配,对将实参转换为对应形参所需的类型转换进行分类。对于类类型的实参和形参,可能的转换的集合包括类类型转换。

Standard Conversions Following Conversion Operator
转换操作符之后的标准转换

Which function is the best match can depend on whether one or more class-type conversions are involved in matching different functions.

哪个函数是最佳匹配,可能依赖于在匹配不同函数中是否涉及了一个或多个类类型转换。

If two functions in the overload set can be matched using the same conversion function, then the rank of the standard conversion sequence that follows or precedes the conversion is used to determine which function has the best match.

如果重载集中的两个函数可以用同一转换函数匹配,则使用在转换之后或之前的标准转换序列的等级来确定哪个函数具有最佳匹配。


Otherwise, if different conversion operations could be used, then the conversions are considered equally good matches, regardless of the rank of any standard conversions that might or might not be required.

否则,如果可以使用不同转换操作,则认为这两个转换是一样好的匹配,不管可能需要或不需要的标准转换的等级如何。


On page 541 we looked at the effect of class-type conversions on calls to functions that are not overloaded. Now, we'll look at similar calls but assume that the functions are overloaded:

第 14.9.3 节中介绍了类类型转换在非重载函数调用上的效果,现在,我们将看看类似的调用,但假定函数是重载的:

     void compute(int);
     void compute(double);
     void compute(long double);

Assuming we use our original SmallInt class that only defines one conversion operatorthe conversion to intthen if we pass a SmallInt to compute, the call is matched to the version of compute that takes an int.

假定使用原来的 SmallInt 类,该类只定义了一个转换操作符——从 SmallIntint 的转换,那么,如果将 SmallInt 对象传给 compute,该调用与接受一个 intcompute 版本相匹配。

All three compute functions are viable:

三个函数都是可行的:

  • compute(int) is viable because SmallInt has a conversion to int. That conversion is an exact match for the parameter.

    compute(int) 可行,因为 SmallInt 有到 int 的转换,该转换是对形参的完全匹配。

  • compute(double) and compute(long double) are also viable, by using the conversion to int followed by the appropriate standard conversion to either double or long double.

    compute(double)compute(long double) 也是可行的,可以使用到 int 的转换,后面跟上适当的用于 doublelong double 的标准转换。

Because all three functions would be matched using the same class-type conversion, the rank of the standard conversion, if any, is used to determine the best match. Because an exact match is better than a standard conversion, the function compute(int) is chosen as the best viable function.

因为可以用同一类类型转换来匹配这三个函数,如果存在标准转换,就用标准转换的等级确定最佳匹配。因为完全匹配比标准转换更好,所以选择 compute(int) 函数作为最佳可行函数。

The standard conversion sequence following a class-type conversion is used as a selection criterion only if the two conversion sequences use the same conversion operation.

只有两个转换序列使用同一转换操作时,才用类类型转换之后的标准转换序列作为选择标准。



Multiple Conversions and Overload Resolution
多个转换和重载确定

We can now see one reason why adding a conversion to double is a bad idea. If we use the revised SmallInt class that defines conversions to both int and double, then calling compute on a SmallInt value is ambiguous:

现在可以看看为什么增加一个到 double 的转换是个坏主意。如果使用修改后的定义了到 intdouble 的转换的 SmallInt 类,则用 SmallInt 值调用 compute 具有二义性:

     class SmallInt {
     public:
         // Conversions to int or double from SmallInt
         // Usually it is unwise to define conversions to multiple arithmetic types
         operator int() const { return val; }
         operator double() const { return val; }
         // ...
     private:
         std::size_t val;
     };
     void compute(int);
     void compute(double);
     void compute(long double);
     SmallInt si;
     compute(si);    // error: ambiguous

In this case we could use the operator int to convert si and call the version of compute that takes an int. Or we could use operator double to convert si and call compute(double).

在这个例子中,可以使用 operator int 转换 si 并调用接受 int 参数的 compute 版本,或者,可以使用 operator double 转换 si 并调用 compute(double)

The compiler will not attempt to distinguish between two different class-type conversions. In particular, even if one of the calls required a standard conversion following the class-type conversion and the other were an exact match, the compiler would still flag the call as an error.

编译器将不会试图区别两个不同的类类型转换。具体而言,即使一个调用需要在类类型转换之后跟一个标准转换,而另一个是完全匹配,编译器仍会将该调用标记为错误。

Explicit Constructor Call to Disambiguate
显式强制转换消除二义性

A programmer who is faced with an ambiguous conversion can use a cast to indicate explicitly which conversion operation to apply:

面对二义性转换,程序员可以使用强制转换来显式指定应用哪个转换操作:

     void compute(int);
     void compute(double);
     SmallInt si;
     compute(static_cast<int>(si)); // ok: convert and call compute(int)

This call is now legal because it explicitly says which conversion operation to apply to the argument. The type of the argument is forced to int by the cast. That type exactly matches the parameter of the first version of compute that takes an int.

这个调用现在是合法的,因为它显式指出了将哪个转换操作应用到实参。实参类型强制转换为 int,该类型与接受 int 参数的第一个 compute 版本完全匹配。

Standard Conversions and Constructors
标准转换和构造函数

Let's look at overload resolution when multiple conversion constructors exist:

现在来看存在多个转换构造函数的重载确定:

     class SmallInt {
     public:
         SmallInt(int = 0);
     };
     class Integral {
     public:
         Integral(int = 0);
     };
     void manip(const Integral&);
     void manip(const SmallInt&);
     manip(10); // error: ambiguous

The problem is that both classes, Integral and SmallInt, provide constructors that take an int. Either constructor could be used to match a version of manip. Hence, the call is ambiguous: It could mean convert the int to Integral and call the first version of manip, or it could mean convert the int to a SmallInt and call the second version.

问题在于,IntegralSmallInt 这两个类都提供接受 int 参数的构造函数,其中任意一个构造函数都可以与 manip 的一个版本相匹配,因此,函数调用有二义性:它既可以表示将 Integral 转换为 int 并调用 manip 的第一个版本,也可以表示 SmallInt 转换为 int 并调用 manip 的第二个版本。

This call would be ambiguous even if one of the classes defined a constructor that required a standard conversion for the argument. For example, if SmallInt defined a constructor that took a short instead of an int, the call manip(10) would require a standard conversion from int to short before using that constructor. The fact that one call requires a standard conversion and the other does not is immaterial when selecting among overloaded versions of a call. The compiler will not prefer the direct constructor; the call would still be ambiguous.

即使其中一个类定义了实参需要标准转换的构造函数,这个函数调用也可能具有二义性。例如,如果 SmallInt 定义了一个构造函数,接受 short 而不是 int 参数,函数调用 manip(10) 将在使用构造函数之前需要一个从 intshort 的标准转换。在函数调用的重载版本中进行选择时,一个调用需要标准转换而另一个不需要,这一事实不是实质性,编译器不会更喜欢直接构造函数,调用仍具有二义性。

Explicit Constructor Call to Disambiguate
显式构造函数调用消除二义性

The caller can disambiguate by explicitly constructing a value of the desired type:

调用者可以通过显式构造所需类型的值而消除二义性:

     manip(SmallInt(10));    // ok: call manip(SmallInt)
     manip(Integral(10));    // ok: call manip(Integral)

Needing to use a constructor or a cast to convert an argument in a call to an overloaded function is a sign of bad design.

在调用重载函数时,需要使用构造函数或强制类型转换来转换实参,这是设计拙劣的表现。



Exercises Section 14.9.4

Exercise 14.44:

Show the possible class-type conversion sequences for each of the following initializations. What is the outcome of each initialization?

为下述每个初始化列出可能的类类型转换序列。每个初始化的结果是什么?

     class LongDouble {
         operator double();
         operator float();
     };
     LongDouble ldObj;
     (a) int ex1 = ldObj;    (b) float ex2 = ldObj;

Exercise 14.45:

Which calc() function, if any, is selected as the best viable function for the following call? Show the conversion sequences needed to call each function and explain why the best viable function is selected.

哪个 calc() 函数是如下函数调用的最佳可行函数?列出调用每个函数所需的转换序列,并解释为什么所选定的就是最佳可行函数。

     class LongDouble {
     public
         LongDouble(double);
         // ...
     };
     void calc(int);
     void calc(LongDouble);
     double dval;

     calc(dval); // which function?


14.9.5. Overloading, Conversions, and Operators

14.9.5. 重载、转换和操作符

Overloaded operators are overloaded functions. The same process that is used to resolve a call to an overloaded function is used to determine which operator built-in or class-typeto apply to a given expression. Given code such as

重载操作符就是重载函数。使用与确定重载函数调用一样的过程来确定将哪个操作符(内置的还是类类型的)应用于给定表达式。给定如下代码:

     ClassX sc;
     int iobj = sc + 3;

there are four possibilities:

有四种可能性:

  • There is an overloaded addition operator that matches ClassX and int.
  • 有一个重载的加操作符与 ClassXint 相匹配。
  • There are conversions to convert sc and/or to convert an int to types for which + is defined. If so, this expression will use the conversion(s) followed by applying the appropriate addition operator.
  • 存在转换,将 sc 和/或 int 值转换为定义了 + 的类型。如果是这样,该表达式将先使用转换,接着应用适当的加操作符。
  • The expression is ambiguous because both a conversion operator and an overloaded version of + are defined.
  • 因为既定义了转换操作符又定义了 + 的重载版本,该表达式具有二义性。
  • The expression is invalid because there is neither a conversion nor an over-loaded + to use.
  • 因为既没有转换又没有重载的 + 可以使用,该表达式非法。
Overload Resolution and Operators
重载确定和操作符

The fact that member and nonmember functions are possible changes how the set of candidate functions is selected.

成员函数和非成员函数都是可能的,这一事实改变了选择候选函数集的方式。



Overload resolution (Section 7.8.2, p. 269) for operators follows the usual three-step process:

操作符的重载确定(第 7.8.2 节)遵循常见的三步过程:

1.
Select the candidate functions.
选择候选函数。
2.
Select the viable functions including identifying potential conversions sequences for each argument.
选择可行函数,包括识别每个实参的潜在转换序列。
3.
Select the best match function.
选择最佳匹配的函数。
Candidate Functions for Operators
操作符的候选函数

As usual, the set of candidate functions consists of all functions that have the name of the function being used, and that are visible from the place of the call. In the case of an operator used in an expression, the candidate functions include the built-in versions of the operator along with all the ordinary nonmember versions of that operator. In addition, if the left-hand operand has class type, then the candidate set will contain the overloaded versions of the operator, if any, defined by that class.

一般而言,候选函数集由所有与被使用的函数同名的函数构成,被使用的函数可以从函数调用处看到。对于操作符用在表达式中的情况,候选函数包括操作符的内置版本以及该操作符的普通非成员版本。另外,如果左操作符具有类类型,而且该类定义了该操作符的重载版本,则候选集将包含操作符的重载版本。

Ordinarily, the candidate set for a call includes only member functions or nonmember functions but not both. When resolving the use of an operator, it is possible for both nonmember and member versions of the operator to be candidates.

一般而言,函数调用的候选集只包括成员函数或非成员函数,不会两者都包括。而确定操作符的使用时,操作符的非成员和成员版本可能都是候选者。



When resolving a call to a named function (as opposed to the use of an operator), the call itself determines the scope of names that will be considered. If the call is through an object of a class type (or through a reference or pointer to such an object), then only the member functions of that class are considered. Member and nonmember functions with the same name do not overload one another. When we use an overloaded operator, the call does not tell us anything about the scope of the operator function that is being used. Therefore, both member and nonmember versions must be considered.

确定指定函数的调用时,与操作符的使用相反,由调用本身确定所考虑的名字的作用域。如果是通过类类型的对象(或通过这种对象的引用或指针)的调用,则只需考虑该类的成员函数。具有同一名字的成员函数和非成员函数不会相互重载。使用重载操作符是时,调用本身不会告诉我们与使用的操作符函数作用域相关的任何事情,因此,成员和非成员版本都必须考虑。

Caution: Conversions and Operators

警告:转换和操作符

Correctly designing the overloaded operators, conversion constructors, and conversion functions for a class requires some care. In particular, ambiguities are easy to generate if a class defines both conversion operators and overloaded operators. A few rules of thumb can be helpful:

正确设计类的重载操作符、转换构造函数和转换函数需要多加小心。尤其是,如果类既定义了转换操作符又定义了重载操作符,容易产生二义性。下面几条经验规则会有所帮助:

  1. Never define mutually converting classesthat is, if class Foo has a constructor that takes an object of class Bar, do not give class Bar a conversion operator to type Foo.

    不要定义相互转换的类,即如果类 Foo 具有接受类 Bar 的对象的构造函数,不要再为类 Bar 定义到类型 Foo 的转换操作符。

  2. Avoid conversions to the built-in arithmetic types. In particular, if you do define a conversion to an arithmetic type, then

    避免到内置算术类型的转换。具体而言,如果定义了到算术类型的转换,则

    • Do not define overloaded versions of the operators that take arithmetic types. If users need to use these operators, the conversion operation will convert objects of your type, and then the built-in operators can be used.

      不要定义接受算术类型的操作符的重载版本。如果用户需要使用这些操作符,转换操作符将转换你所定义的类型的对象,然后可以使用内置操作符。

    • Do not define a conversion to more than one arithmetic type. Let the standard conversions provide conversions to the other arithmetic types.

      不要定义转换到一个以上算术类型的转换。让标准转换提供到其他算术类型的转换。

The easiest rule of all: Avoid defining conversion functions and limit nonexplicit constructors to those that are "obviously right."

最简单的规则是:对于那些“明显正确”的,应避免定义转换函数并限制非显式构造函数。


Conversions Can Cause Ambiguity with Built-In Operators
转换可能引起内置操作符的二义性

Let's extend our SmallInt class once more. This time, in addition to a conversion operator to int and a constructor from int, we'll give our class an overloaded addition operator:

我们再次扩展 SmallInt 类。这一次,除了到 int 的转换操作符和接受 int 参数的构造函数之外,将增加一个重载的加操作符:

     class SmallInt {
     public:
         SmallInt(int = 0); // convert from int to SmallInt
         // conversion to int from SmallInt
         operator int() const { return val; }
         // arithmetic operators
         friend SmallInt
         operator+(const SmallInt&, const SmallInt&);
     private:
          std::size_t val;
     };

Now we could use this class to add two SmallInts, but we will run into ambiguity problems if we attempt to perform mixed-mode arithmetic:

现在,可以用这个类将两个 SmallInts 对象相加,但是,如果试图进行混合模式运算,将会遇到二义性问题:

     SmallInt s1, s2;
     SmallInt s3 = s1 + s2;         // ok: uses overloaded operator+
     int i = s3 + 0;                // error: ambiguous

The first addition uses the overloaded version of + that takes two SmallInt values. The second addition is ambiguous. The problem is that we could convert 0 to a SmallInt and use the SmallInt version of +, or we could convert s3 to int and use the built-in addition operator on ints.

第一个加使用接受两个 SmallInt 值的 + 的重载版本。第二个加有二义性,问题在于,可以将 0 转换为 SmallInt 并使用 +SmallInt 版本,也可以将 s3 转换为 int 值并使用 int 值上的内置加操作符。

Providing both conversion functions to an arithmetic type and over-loaded operators for the same class type may lead to ambiguities between the overloaded operators and the built-in operators.

既为算术类型提供转换函数,又为同一类类型提供重载操作符,可能会导致重载操作符和内置操作符之间的二义性。



Viable Operator Functions and Conversions
可行的操作符函数和转换

We can understand the behavior of these two calls by listing the viable functions for each call. In the first call, there are two viable addition operators:

通过为每个调用列出可行函数,可以理解这两个调用的行为。在第一个调用中,有两个可行的加操作符:

  • operator+(const SmallInt&, const SmallInt&)

  • The built-in operator+(int, int)

    内置的 operator+(int, int)

The first addition requires no conversions on either argument s1 and s2 match exactly the types of the parameters. Using the built-in addition operator for this addition would require conversions on both arguments. Hence, the overloaded operator is a better match for both arguments and is the one that is called. For the second addition

第一个加不需要实参转换——s1s2 与形参的类型完全匹配。使用内置加操作符对两个实参都需要转换,因此,重载操作符与两个实参匹配得较好,所以将调用它。对于第二个加运算:

     int i = s3 + 0;          // error: ambiguous

the same two functions are viable. In this case, the overloaded version of + matches the first argument exactly, but the built-in version is an exact match for the second argument. The first viable function is better for the left operand, whereas the second viable function is better for the right operand. The call is flagged as ambiguous because no best viable function can be found.

两个函数同样可行。在这种情况下,重载的 + 版本与第一个实参完全匹配,而内置版本与第二个实参完全匹配。第一个可行函数对左操作数而言较好,而第二个可行函数对右操作数而言较好。因为找不到最佳可行函数,所以将该调用标记为有二义性的。

Exercises Section 14.9.5

Exercise 14.46:

Which operator+, if any, is selected as the best viable function for the addition operation in main? List the candidate functions, the viable functions, and the type conversions on the arguments for each viable function.

对于 main 中的加操作,哪个 operator+ 是最佳可行函数?列出候选函数、可行函数以及对每个可行函数中实参的类型转换。

     class Complex {
         Complex(double);
         // ...
     };
     class LongDouble {
         friend LongDouble operator+(LongDouble&, int);
     public:
         LongDouble(int);
         operator double();
         LongDouble operator+(const complex &);
         // ...
      };
     LongDouble operator+(const LongDouble &, double);
     LongDouble ld(16.08);
     double res = ld + 15.05; // which operator+ ?


Team LiB
Previous Section Next Section