Learning notes of Effective C + + (Clause 24: if all parameters need type conversion, please use non member functions)

Recently, I started to watch Effective C + +. In order to facilitate future review, I specially took notes. If I misunderstand the knowledge points in the book, please correct!!!

As mentioned in the introduction, it is not good to make classes support implicit type conversion. However, there are exceptions. The most common exception is to use a class to represent rational numbers when creating numerical types. It is reasonable to support the implicit conversion of int to rational numbers. In addition, the built-in types of C + + also support a variety of implicit conversions, such as from int to double. We can also write this rational number class as follows:

class Rational{
    Rational(int numerator = 0, int denominator = 1);	//Doors are not declared explicit to allow implicit conversion from int to Rational
    int numerator() const;								//Molecular access function
    int denominator() const;							//Access function for denominator

Since the above class is rational, it certainly supports arithmetic operations such as addition and multiplication. Should we use member functions or non member functions, or non member non friend functions? According to our intuition and object-oriented view, rational number multiplication should be implemented in class, that is, member function. but Article 23 Counter intuitively, putting a function into a related class sometimes conflicts with the object-oriented code, but let's put it aside and write operator * as a Rational member function:

class Rational{
    //(see why const is returned in Clause 3, why reference passing is used in Clause 20, and why reference is not returned in Clause 21)
    const Rational operator*(const Rational& rhs) const;

This design allows two Rational objects to be multiplied:

Rational oneEighth(1,8);
Rational oneHalf(1,2);
Rational result = oneEighth * oneHalf; //Compile passed
result = result * oneEighth; //Compile passed

When you try to mix operations (that is, multiply Rational objects and int objects), only half of them work:

result = oneHalf * 2; //Compile passed
result = 2 * oneHalf; //Compilation error

Multiplication should satisfy the commutative law, so the result is not what we want.

Rewrite the above two formulas in the form of corresponding functions:

result = oneHalf.operator*(2); //Compile passed
result = 2.operator*(oneHalf); //Compilation error

The problem is clear at a glance. oneHalf is a class object containing the operator * function, so the compiler calls the function. Integer 2 has no corresponding class, so there is no operator * member function. The compiler will also try to find a non member operator * (that is, within the namespace or global scope) that can be called as follows:

result = operator*(2, oneHalf);

If it cannot be found, the compilation fails.

But do we have some questions about the first successful statement? Why is the input parameter of the operator we define a Rational object, but the integer 2 passed in can also be compiled? In fact, this is an implicit conversion. The compiler implicitly calls Rational's constructor with the 2 we passed in, so it looks like this in the eyes of the compiler:

const Rational tmp(2);		//Construct a transient Rational object with integer 2
result = oneHalf * tmp;	

Of course, the compiler will only do this because the non explicit constructor is declared. If the constructor is declared explicit, the following statements will be compiled incorrectly:

result = oneHalf * 2; //Compilation error, cannot convert 2 to a Rational object with explicit constructor
result = 2 * oneHalf; //The same mistake, the same problem

Remember that only parameters that appear in the parameter table can be implicitly converted. For the first call, the integer 2 is on the right side of *, indicating that it is in the parameter list, that is, 2 is the formal parameter rhs, and no longer the parameter in the parameter list, that is, the object calling the member function, that is, what this points to, cannot be implicitly converted. The second call to integer 2 is on the left side of *, indicating that it called the member function, so the compilation failed.

If you must support mixed operation, you can turn operator * into a non member function, and the objects involved in the operation are in the parameter list, that is, they can be implicitly converted:

class Rational{...};//The operator * function is not declared in the class

//operator * as a non member function
const Rational operator*(const Rational& lhs, const Rational& rhs)
  return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());

int main()
    Rational oneFourth(1,4);
    Rational result;
    result = oneFourth * 2; //Compile passed
    result = 2 * oneFourth; //Compile passed
    return 0;

In addition to non member functions, can we use friend functions to implement operator *? In this case, the answer is No. Because we can realize the desired function from Rational's public interface, the above code has shown this practice. A conclusion is drawn: the opposite of member function is non member function, not friend function. Too many C + + programmers will have a misunderstanding: if a function is related to a class and cannot be used as a member function, it is a friend function. This example proves that this idea is unnecessary and can avoid using friend functions.

Clause 25: consider writing a swap function that does not throw exceptions

Tags: C++

Posted on Wed, 10 Nov 2021 16:52:02 -0500 by ed01