If you are designing a class to represent rational numbers, allowing implicit conversions from integers to rationals does not seem unreasonable.
Let us see the Rational
class :
class Rational{
public:
Rational(int numberator = 0, int denominator = 1); // not explicit;
int numberator() const;
int denominator() const;
private:
};
You would like to support arithmetic operations like addition, multiplication, etc, but you are not sure whether you should implement then via member functions , non-member functions ,or non-member functions that are friends. As the oop, it seems natural to implement operator*
for rational numbers inside the Rational
class.
class Rational{
public:
...
const Rational operator*(const Rational& rhs) const;
};
The design lets you multiply rationals with the greatest of case :
Rational oneEighth(1, 8);
Rational oneHalf(1,2);
Rational result = oneHalf * oneEighth; // fine
result = result * oneHalf; // fine
It seems well, and you would like to support mixed-mode operations , where Rational
can be multiplied with, for example, int
.
When you try to do mixed-mode arithmetic, however, you find that it works only half the time :
result = oneHalf * 2; //fine
result = 2 * oneHalf; // error
Because these two examples in their equivalent function form :
result = oneHalf.operator*(2); // fine
result = 2.operator*(oneHalf); // error
Because of the implicit type conversion, the first example is like this :
const Rational temp(2);
result = oneHalf * temp;
Compilers do this only because a non-explicit constructor is involved. If Rational
's constructor were explicit
, neither of these statements would compile :
result = oneHalf * 2; //error
result = 2 * oneHalf; // error
For the second example, 2 has not corresponding operator*
with Rational
object and it is not reason to make a int
to Rational
object.This is another easy question.
You would still like to support mixed-mode arithmetic, however , and the way to do it is by now perhaps clear : make operator*
a non-member function, thus allowing compilers to perform implicit type conversions on all arguments :
class Rational{
...
};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numberator(), lhs.denominator() * rhs.denominator() );
}
Rational oneFourth(1,4);
Rational result;
result = oneFourth * 2; // fine
result = 2 * oneFourth; //fine
Conclusion
- If you need type conversions on all parameters to a function(including the one pointed to by the
this
pointer), the function must be a non-member.
Example:
class Rational{
public:
Rational(int numberator = 0, int denominator = 1){
this -> numberator = numberator;
this -> denominator = denominator;
}
int Getnumberator() const
{
return numberator;
}
int Getdenominator() const
{
return denominator;
}
void show()
{
cout << numberator << '\t' << denominator <<endl;
}
const Rational operator*(const Rational& r)
{
Rational result(this->numberator * r.Getnumberator(), this->denominator * r.Getdenominator());
return result;
}
private:
int numberator;
int denominator;
};
int main(int argc, char const *argv[])
{
Rational a(10);
Rational res = a * 2;
a.show(); //10 1
res.show(); //20 1
Rational resv = 2 * a; //error: invalid operands to binary expression ('int' and 'Rational')
return 0;
}
And we make some change :
delete the opeartor*
in the class Rational
and define it as a non-member function
const Rational operator*(const Rational& l, const Rational& r)
{
Rational ret(l.Getnumberator() * r.Getnumberator(), l.Getdenominator() * r.Getdenominator());
return ret;
}
int main(int argc, char const *argv[])
{
Rational a(10);
Rational res = a * 2;
a.show(); //10 1
res.show(); //20 1
Rational resv = 2 * a;
resv.show();//20 1
return 0;
}