C++ member function and non member function choice

  • 2020-04-01 21:29:03
  • OfStack

1. Try to replace the member functions of the class with non-member functions and friend functions
For example, a class to simulate People
 
1 class People{ 
2 public: 
3 ... 
4 void Getup( ); 
5 void Washing( ); 
6 void eating( ); 
7 ... 
8 } 

In fact, the above three actions are the three common actions of "getting up", "washing" and "eating" in the morning

1 class People 
2 { 
3 ... 
4 void morningAction( ) 
5 { 
6 Getup( ); 
7 Washing( ); 
8 eating( ); 
9 } 
10 } 

If I write a non-member function, I get zero
 
1 void moringAction(People& p) 
2 { 
3 p.Getup( ); 
4 p.Washing( ); 
5 p.eating( ); 
6 } 

So is it to choose a member function of a class or a nonmember function of a class?

Object orientation requires that functions that manipulate data be placed with the data. But that doesn't mean you have to choose a member function. From the perspective of encapsulation, the moringAction encapsulation of member functions is lower than that of non-member functions. If something is encapsulated, it is no longer visible. The more things are encapsulated, the less people see them. So classes that use non-member functions are less encapsulated. And the less people see it, the more flexibility we have to change it, because our change only directly affects those who see it. Therefore, the more things are encapsulated, the greater the ability to change those things.

Consider the data within the object. The less code we have to see the data (access it), the more data we can encapsulate, and the more freedom we have to change the object data. Now if a member function and a non-member function both provide the same functionality, we choose the non-member function.

Before we move on, let's talk about type-casting in C++ : displayer type-casting and implicit type-casting.

2. Display type conversions

C++ has the display type conversion operators static_cast,const_cast,dynamic_cast, and reinterpret_cast


2.1 static_cast
Conversions are the same as c-style conversions and have the same meaning. Types without inheritance can be converted by using static_cast. Note, however, that you cannot convert a built-in type to a custom type, or a custom type to a built-in type, and you cannot remove a cosnt type, but you can convert a non-cosnt type to a const type. Such as:
Char a = 'a';

Int b = static_cast < Int> (a);
Conversion between the two types, in fact, is to implement their own support, the principle and built-in type conversion between the same.
 
1 #include <iostream> 
2 class Car; 
3 
4 class People 
5 { 
6 public: 
7 People(int a,int h) 
8 :age(a),height(h) 
9 {} 
10 
11 inline int getAge() const 
12 { 
13 return age; 
14 } 
15 
16 inline int getHeight() const 
17 { 
18 return height; 
19 } 
20 
21 People & operator=(const Car& c); 
22 private: 
23 int age; 
24 int height; 
25 }; 
26 
27 class Car 
28 { 
29 public: 
30 Car(double c, double w) 
31 :cost(c),weight(w) 
32 {} 
33 
34 inline double getCost() const 
35 { 
36 return cost; 
37 } 
38 
39 inline double getWeight() const 
40 { 
41 return weight; 
42 } 
43 
44 Car & operator=(const People& c); 
45 private: 
46 double cost; 
47 double weight; 
48 }; 
49 
50 People & People::operator=(const Car& c) 
51 { 
52 age = static_cast<int>(c.getCost()); 
53 height = static_cast<int>(c.getWeight()); 
54 return *this; 
55 } 
56 
57 Car & Car::operator=(const People& c) 
58 { 
59 cost = static_cast<double>(c.getAge()); 
60 weight = static_cast<double>(c.getHeight()); 
61 return *this; 
62 } 
63 
64 int main(int argc,char * argv[]) 
65 { 
66 Car c(1000.87,287.65); 
67 People p(20,66); 
68 People p2(0,0); 
69 Car c2(0.00,0.00); 
70 p2=c; 
71 c2=p; 
72 std::cout<< "car'info: cost is " << c2.getCost() << ". weight is " << c2.getWeight() <<std::endl; 
73 std::cout<< "people'info: age is " << p2.getAge() <<". height is " << p2.getHeight() <<std::endl; 
74 return 0; 
75 } 

The running result is
Car 'info: cost is 20. Weight is 66
People 'info: age is 1000. height is 287

2.2 const_cast
Is mainly used to remove the const and volatileness attributes, in most cases are used to remove the const limit.


2.3 dynamic_cast
It is used to safely cast down inheritance relationships. Dynamic_cast is used to convert a pointer or reference to a base class to a pointer or reference to a derived class, and when the conversion fails, a null pointer is returned.


2.4 reinterprit_cast
The most common use of this conversion is to convert between function pointer types.
 
1 typedef void (*fun) ( ) //A pointer to a null function
2 fun funArray[10]; //Data containing 10 function Pointers.
3 int function( ); //A function whose return value is of type int
4 funArray[0] = &function( ) //Error! Type mismatch
5 funArray[0] = reinterpret_cast<fun>(&function); //Ok note that the code for the transformation function pointer is not portable.

You should generally avoid converting function pointer types.

3. Implicit conversions can occur using non-member functions
C++ supports implicit type conversions, such as when performing operations or passing arguments to functions.
Suppose you design a class to represent rational Numbers. Getting classes to support implicit type conversions was a bad decision. The exception, of course, is when creating numeric types. Here is the definition of a rational number type:
 
1 class Rational { 
2 public: 
3 Rational( int numerator = 0,int denominator =1 ); 
4 int numerator( ) const; 
5 int denominator ( ) const ; 
6 private: 
7 ... 
8 } 

Rational number types naturally support arithmetic operations, but it is not certain whether to declare them as member or non-member functions or friend functions to implement them.

The first is to consider the member function writing:
 
1 class Rational { 
2 public: 
3 ... 
4 const Rational operator* (const Rational& rhs) const; 
5 ... 
6 } 
7 Rational testOne(1,4); 
8 Rational testTwo(1,1); 
9 //Do arithmetic
10 Rational result = testOne * testTwo; 
11 //I'm going to do it with constants
12 result = testOne * 2; //ok 
13 //Multiplication satisfies the exchange law
14 result = 2 * testOne //error!! 

So why would it be wrong to bring the constant forward? So let's write it a different way
 
1 result = testOne.operator*(2); //ok 
2 result =2.operator*(oneHalf); //error 

What's going on here? In fact, there's what's called implicit typecasting going on in the first row. Where does implicit casting occur? See the above code operator* function parameter is of type const Rational, and 2 is of type cosnt int,. The compiler finds that there are non-explicit single-argument class constructors of type int that can build Rational types. So the compiler does that, and implicitly casts. So the first one works fine, but the second one doesn't convert Rational to int.
It is only reasonable to design the above two equations to work properly.
 
1 class Rational{ 
2 ... 
3 }; 
4 const Rational operator*(const Rational & lhs, const Rational & rhs) 
5 { 
6 return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator() ); 
7 } 
8 Rational testOne(1, 4); 
9 Rational result; 
10 result = oneFourth *2; 
11 result = 2 * oneFourth;  through  
12 } 

The code above is designed as a non-member function, so that implicit type conversion occurs when an int integer is called.
Should operaotr* be called a friend function of Rational class? For this example, it is not necessary at all.

If you need to cast all the arguments to a function, the function must be a nonmember function.

4. Use implicit casting with caution
There are some types of implicit conversions that we can't do anything about because they are a feature of the language itself. However, when writing custom classes, we can choose whether or not to provide functions for the compiler to implicitly cast.
There are two types of functions that allow the compiler to do implicit type conversions: single-argument constructors and implicit type conversions operators.
 
1 public Name{ 
2 public: 
3 Name(const string& s); //Convert string to Name
4 
5 ... 
6 }; 
7 
8 class Rational { //Rational class
9 public: 
10 // Conversion from int to Rational class
11 Rational(int numerator=0,int denominatior =1); 
12 ... 
13 } 

C++ supports implicit type conversions, such as when performing operations or passing arguments to functions. There are two types of functions that allow the compiler to do implicit conversions. Single-argument constructors and implicit type conversion operators.

While we may have covered some of the benefits of implicit casting, here are some of the pitfalls.
 
1 template<class T> 
2 class Array{ 
3 Array(int lowBound,int highBound); 
4 Array(int size); 
5 T& operator[](int index); 
6 ... 
7 }; 
8 bool oerpator==(const Array<int>& lhs,const Array<int>& rhs); 
9 Array<int> a(10); 
10 Array<int> b(10); 
11 ... 
12 for(int i=0;i < 10; ++i) 
13 if(a == b[i]) { 
14 ... } 
15 else 
16 { 
17 ... 
18 } 

If I accidentally forgot to write the index of array a here, the compiler should give me a warning, but it doesn't. Because the compiler sees a as Array< Int> Type, b is int, based on the above experience, the compiler sees a non-explicit single-parameter constructor of type int, and the operator needs an Array< Int> Type, so the compiler does that, and implicitly casts. Believe that if this happens, it will be very troublesome.
How to avoid it? Declare the constructor as explicit. Avoid implicit type conversions.

Related articles: