当类型转换需应用于所有参数时,非成员函数与成员函数的差异解析(基于《Effective C++》)
咱们用《Effective C++》里的有理数类例子,聊聊为啥当需要给所有参数做隐式类型转换时,非成员函数比成员函数更靠谱。
1. 把operator*设为成员函数
先看代码实现:
class Rational { public: Rational(int numerator=0,int denominator=1) : numerator(numerator), denominator(denominator){ std::cout << "constructor be called" << std::endl; } const Rational operator*(const Rational& rhs){ return Rational(numerator * rhs.numerator, denominator * rhs.denominator); // 成员函数版本 } private: int numerator; int denominator; }; Rational oneHalf(1,2); Rational result = 2 * oneHalf; // 这里会报错!
为啥2 * oneHalf会报错?其实你可以把operator*的调用逻辑拆开看:成员函数的调用规则是左边的操作数必须是当前类的实例。比如oneHalf * 2其实是oneHalf.operator*(2),这时候2能被隐式转换成Rational对象,完全没问题;但反过来2 * oneHalf相当于2.operator*(oneHalf)——int类型根本没有operator*这个成员函数,编译器也不会帮你把2转换成Rational对象来充当调用成员函数的“主体”。
2. 把operator*设为非成员函数
换成全局非成员函数后,代码是这样的:
class Rational { public: Rational(int numerator=0,int denominator=1) : numerator(numerator), denominator(denominator){ std::cout << "constructor be called" << std::endl; } private: int numerator; int denominator; // 声明为友元,让非成员函数能访问私有成员 friend const Rational operator*(const Rational& lhs,const Rational& rhs); }; const Rational operator*(const Rational& lhs,const Rational& rhs){ return Rational(lhs.numerator * rhs.numerator, lhs.denominator * rhs.denominator); // 非成员函数版本 } Rational oneHalf(1,2); Rational result = 2 * oneHalf; // 这下正常运行了!
这时候为啥没问题?因为非成员函数的两个参数是完全平等的,编译器会尝试给每一个参数都做隐式类型转换。2 * oneHalf会被编译器转换成operator*(Rational(2), oneHalf)——2被隐式转换成Rational对象,完美匹配函数的参数列表,自然就能正常调用了。
为啥成员函数的this对象不能做隐式转换?
其实核心逻辑特别简单:成员函数是依附于某个类实例存在的,你要调用它,必须先有一个合法的类对象当“调用主体”——这个主体就是this指针指向的对象。编译器绝对不会帮你把一个其他类型的值转换成类对象来当这个主体,因为这相当于凭空造了一个对象来调用成员函数,不符合C++的设计逻辑。
举个生活化的例子:假设你有个Person类,有个成员函数buyCoffee()。你可以说张三.buyCoffee(),但你绝对不能说123.buyCoffee()——编译器不会自动把数字123转换成一个Person对象来调用buyCoffee()。但如果是个非成员函数buyCoffee(Person p),你传123进去,只要Person有接受int的构造函数,编译器就会帮你把123转成Person对象传进去。
回到咱们的有理数例子:成员版的operator*,左边的操作数必须是Rational实例,因为它是这个实例的成员函数;而非成员版的两个参数都是普通参数,编译器会给每个参数都尝试做隐式转换,不管是左边还是右边。
内容的提问来源于stack exchange,提问作者jptang

