1、Terms21:通过重载避免隐式类型转换
1.1 代码分析
以下是一段代码,如果没有什么不寻常的原因,实在看不出什么东西:
class UPInt { // unlimited precision
public: // integers 类
UPInt();
UPInt(int value);
...
};
const UPInt operator+(const UPInt& lhs, const UPInt& rhs);
UPInt upi1, upi2;
...
UPInt upi3 = upi1 + upi2;
这里还看不出什么令人惊讶的东西。upi1 和upi2都是UPInt对象,所以它们之间相加就会调用UPInts的operator函数。
现在考虑下面这些语句:
upi3 = upi1 + 10;
upi3 = 10 + upi2;
这些语句也能成功运行。方法是通过建立临时对象把整数10转为UPInts
让编译器完成这种类型转换是很方便,但是建立临时对象进行类型转换工作是有开销的,而我们不想承担这种开销。大多数C++程序员希望进行没有临时对象开销的隐式类型转换,但是在计算领域里发生不了赤字现象,我们如何能这么做呢?
让我们回退一步,认识到我们的目的不是真的要进行类型转换,而是用UPint和int做为参数调用operator+。隐式类型转换只是用来达到目的的手段,但是我们不要混淆手段与目的。还有一种方法可以成功进行operator+的混合类型调用,它将消除隐式类型转换的需要。如果我们想要把UPInt和int对象相加,通过声明如下几个函数达到这个目的,每一个函数有不同的参数类型集。
const UPInt operator+(const UPInt& lhs, const UPInt& rhs); // add UPInt and UPInt
const UPInt operator+(const UPInt& lhs, int rhs); // add UPInt and int
const UPInt operator+(int lhs, const UPInt& rhs); // add int and UPInt
UPInt upi1, upi2;
...
UPInt upi3 = upi1 + upi2; // 正确,没有由upi1 或 upi2生成的临时对象
upi3 = upi1 + 10; // 正确, 没有由upi1 or 10生成的临时对象
upi3 = 10 + upi2; //正确, 没有由10 or upi2生成的临时对象。
一旦你开始用函数重载来消除类型转换,你就有可能这样声明函数,把自己陷入危险之中:
const UPInt operator+(int lhs, int rhs); // 错误!
这个想法是合情合理的。对于UPInt和int类型,我们想要用所有可能的组合来重载operator函数。上面只给出了三种重载函数,唯一漏掉的是带有两个int参数的operator,所以我们想把它加上。有道理么?
在C++中有一条规则是每一个重载的operator必须带有一个用户定义类型(user-defined type)的参数
。
int不是用户定义类型,所以我们不能重载operator成为仅带有此[int]类型参数的函数。(如果没有这条规则,程序员将能改变预定义的操作,这样做肯定把程序引入混乱的境地。比如企图重载上述的operator,将会改变int类型相加的含义。)
利用重载避免临时对象的方法不只是用在operator函数上。比如在大多数程序中,你想允许在所有能使用string对象的地方,也一样可以使用char*,反之亦然。同样如果你正在使用numerical(数字)类,例如complex,你想让int和double这样的类型可以使用在numerical对象的任何地方。因此任何带有string、char*、complex参数的函数可以采用重载方式来消除类型转换。
不过, 没有必要实现大量的重载函数,除非你有理由确信程序使用重载函数以后其整体效率会有显著的提高。
1.2 可编译的示例
#include <iostream>
#include <vector>
class UPInt {
public:
std::vector<int> digits;
// 默认构造函数
UPInt() {}
// 从 int 构造 UPInt
UPInt(int value) {
while (value > 0) {
digits.push_back(value % 10);
value /= 10;
}
}
// 重载加法运算符(UPInt + UPInt)
friend const UPInt operator+(const UPInt& lhs, const UPInt& rhs);
// 重载加法运算符(UPInt + int)
friend const UPInt operator+(const UPInt& lhs, int rhs);
// 输出 UPInt
void print() const {
if (digits.empty()) {
std::cout << 0;
} else {
for (auto it = digits.rbegin(); it != digits.rend(); ++it) {
std::cout << *it;
}
}
std::cout << std::endl;
}
};
// 实现重载的加法运算符(UPInt + UPInt)
const UPInt operator+(const UPInt& lhs, const UPInt& rhs) {
UPInt result;
int carry = 0;
int i = 0, j = 0;
while (i < lhs.digits.size() || j < rhs.digits.size() || carry > 0) {
int sum = carry;
if (i < lhs.digits.size()) {
sum += lhs.digits[i++];
}
if (j < rhs.digits.size()) {
sum += rhs.digits[j++];
}
carry = sum / 10;
result.digits.push_back(sum % 10);
}
return result;
}
// 实现重载的加法运算符(UPInt + int)
const UPInt operator+(const UPInt& lhs, int rhs) {
UPInt temp(rhs);
return lhs + temp;
}
// 由于加法是可交换的,我们可以使用相同的函数来处理 int + UPInt
const UPInt operator+(int lhs, const UPInt& rhs) {
return rhs + lhs; // 利用 UPInt + int 的实现
}
int main() {
UPInt upi1(123);
UPInt upi2(456);
UPInt upi3 = upi1 + 10;
upi3.print(); // 输出:133
upi3 = 10 + upi2;
upi3.print(); // 输出:466
return 0;
}
2、总结
可能会提高效率,也可能不会,根据实际情况自己斟酌。
3、参考
《More Effective C++》