14.9. Conversions and Class Types14.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 Useful14.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 对象的混合模式使用的要求。但是,这个设计仅仅接近内置整数运算的行为,它不能适当处理浮点类型混合模式操作,也不能适当支持 long 、unsigned int 或 unsigned 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 而言,可以定义一个从 SmallInt 到 int 类型的转换。如果定义了该转换,则无须再定义任何算术、关系或相等操作符。给定到 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 可这样确定:
14.9.2. Conversion Operators14.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 之外)都可以定义转换函数。一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的。
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 对象,诸如此类。
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 节):
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. 首先将 si 从 SmallInt 对象转换为 int 值,然后将该 int 值转换为 double 值。 Only One Class-Type Conversion May Be Applied只能应用一个类类型转换
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 调用是错误的:没有从 Integral 到 int 的直接转换。从 int 需要两次类类型转换:首先从 Integral 到 SmallInt,然后从 SmallInt 到 int。但是,语言只允许一次类类型转换,所以该调用出错。 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(),应用标准转换将 dobj 从 double 类型转换为 int 类型,然后调用构造函数 SmallInt(int) 将转换结果转换为 SmallInt 类型。
14.9.3. Argument Matching and Conversions14.9.3. 实参匹配和转换
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. 类类型转换也可能是编译时错误的一大来源。当从一个类型转换到另一类型有多种方式时,问题就出现了。如果有几个类类型转换可以使用,编译器必须决定对给定表达式使用哪一个。在这一节,我们介绍怎样用类类型转换将实参和对应形参相匹配。首先介绍非重载函数的形参匹配,然后介绍重载函数的形参匹配。
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; };
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 调用中:
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,因此,没有一个转换比其他的更好,调用具有二义性。
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。使用每一个构造函数之前都需要对实参进行转换:
These conversion sequences are indistinguishable, so the call is ambiguous. 这些转换序列是不能区别的,所以该调用具有二义性。
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 类的转换操作符可以避免这个额外的步骤。这一小小区别足以使我们倾向于使用转换操作符。
14.9.4. Overload Resolution and Class Arguments14.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 节)由三步组成:
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. 哪个函数是最佳匹配,可能依赖于在匹配不同函数中是否涉及了一个或多个类类型转换。
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 类,该类只定义了一个转换操作符——从 SmallInt 到 int 的转换,那么,如果将 SmallInt 对象传给 compute,该调用与接受一个 int 的 compute 版本相匹配。 All three compute functions are viable: 三个函数都是可行的:
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) 函数作为最佳可行函数。
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 的转换是个坏主意。如果使用修改后的定义了到 int 和 double 的转换的 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. 问题在于,Integral 和 SmallInt 这两个类都提供接受 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) 将在使用构造函数之前需要一个从 int 到 short 的标准转换。在函数调用的重载版本中进行选择时,一个调用需要标准转换而另一个不需要,这一事实不是实质性,编译器不会更喜欢直接构造函数,调用仍具有二义性。 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)
14.9.5. Overloading, Conversions, and Operators14.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: 有四种可能性:
Overload Resolution and Operators重载确定和操作符
Overload resolution (Section 7.8.2, p. 269) for operators follows the usual three-step process: 操作符的重载确定(第 7.8.2 节)遵循常见的三步过程:
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. 一般而言,候选函数集由所有与被使用的函数同名的函数构成,被使用的函数可以从函数调用处看到。对于操作符用在表达式中的情况,候选函数包括操作符的内置版本以及该操作符的普通非成员版本。另外,如果左操作符具有类类型,而且该类定义了该操作符的重载版本,则候选集将包含操作符的重载版本。
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. 确定指定函数的调用时,与操作符的使用相反,由调用本身确定所考虑的名字的作用域。如果是通过类类型的对象(或通过这种对象的引用或指针)的调用,则只需考虑该类的成员函数。具有同一名字的成员函数和非成员函数不会相互重载。使用重载操作符是时,调用本身不会告诉我们与使用的操作符函数作用域相关的任何事情,因此,成员和非成员版本都必须考虑。
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 值上的内置加操作符。
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: 通过为每个调用列出可行函数,可以理解这两个调用的行为。在第一个调用中,有两个可行的加操作符:
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 第一个加不需要实参转换——s1 和 s2 与形参的类型完全匹配。使用内置加操作符对两个实参都需要转换,因此,重载操作符与两个实参匹配得较好,所以将调用它。对于第二个加运算:
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. 两个函数同样可行。在这种情况下,重载的 + 版本与第一个实参完全匹配,而内置版本与第二个实参完全匹配。第一个可行函数对左操作数而言较好,而第二个可行函数对右操作数而言较好。因为找不到最佳可行函数,所以将该调用标记为有二义性的。
![]() |