A few things you'd better not do with C++

  • 2020-04-01 21:28:46
  • OfStack

1. It is best not to use a reference return value

Some students use the method of reference when passing the parameters, which avoids the creation of temporary objects and improves the efficiency. Can we use the reference when returning the value?

Look at the following code


        class Rational{
        public:
            Raional( int numerator = 0, int denominator =1);
            ...
        private:
            int d, d;
            friend Rational operator* (const Rational& lhs, const Raional& rhs) ;
        };
       Rational Rational::operator* (const Rational& lhs,const Raionl&rhs)
        {
                return Rational  result(lhs.n*rhs.n,lhs.d*rhs.d);
        } 
     } 
     
      This class is the rational number class we introduced earlier. Consider that there is a class construct and class destruct, so can you avoid this problem by using references? To increase efficiency?

The function creates a new object in a stack (statck) and a heap (heep).


        People p(a,b)                  //The stack to create
        People *p = new People(a,b)   //The heap to create
 
  First consider creating it on the stack, but the created variable is a local variable that will be destroyed before exiting the function.

   const Rational& operator* (const Rational& lhs, const Rational & rhs)
        {
            Rational  result(lhs.n*rhs.n,lhs.d*rhs.d);
            return result;
        }  

  Objects created in stack-style space within functions are local objects, and any function that returns a reference to a local object will have an error. Because the local object is destroyed before the function exits, it means that the object referred to by reference does not exist.
          So consider creating it in the heap

const Rational& operator* (const Rational& lhs, const Rational & rhs)
        {
            Rational*  result=new Rational(lhs.n*rhs.n,lhs.d*rhs.d);
            return *result;
        }  
 
  Now there is another problem: who will delete the new object? Ok, so let's not worry about this when we're preoccupying

          Rational w,x,y,z;
          w=x*y*z;      
   
    The operator* is called twice by the same statement, meaning new twice and delete twice. But there is no reasonable way for the opertaor* consumer to make those delete calls, because there is no way for the consumer to get the returned pointer, which leads to a resource leak.
          Consider returning a reference to a static Rational object defined inside a function.

const Rational & operator*(const Rational& lhs,const Rational & rhs)
        {
            static Rational result;
            result = ...;
            return result;
        }
 
  So multithreading, obviously, is that safe to write in a multithreaded environment? Ok, let's say that we're not talking about multithreading. So what happens to the following code?

  bool operator == (const Rational& lhs, const Rational&  rhs);
    ...
    Raional a,b,c,d;
    if((a*b) == (c*d) 
    {
            ...
    }    
     
      The above if statement expression is true regardless of the values of a,b,c, and d, because they all point to the same static value.

2. It is best not to put all variable definitions at the beginning of a statement.

Some of you may have taken courses in C and like to learn C, and like to put all the variable definitions at the beginning, but in C++, I recommend that you do not do this, because when you define a variable, the program is bound to need a construction and destruction. We allow students under 1.8m and under the age of 60 to buy a ticket to enter.


 class People{...};
 class Ticket{...};
 bool Isvalid(const People&p){...}
 void Banding(const People& p,Ticket& t); 
 Ticket buyTicket(const People& p)
 {
     Ticket t;
     if(Isvalid(p)){ return NULL };
     //The information is bound to the ticket
    Banding(p,&t);
    return t;
}

If the Ticket buyer condition is not met, then the Ticket cannot be entered to carry out information and binding operations. Then the Ticket t statement makes the function bear the cost of Ticket construction and destruction in vain.
Therefore, it is best not to define variables in advance and when they are needed to avoid unnecessary performance overhead. The above example can be changed to the following:


 class People{...};
 class Ticket{...};
 bool Isvalid(const People&p){...}
 void Banding(const People& p,Ticket& t); 
 Ticket buyTicket(const People& p)
 {
     if(Isvalid(p)){ return NULL };
     Ticket t;
     //The information is bound to the ticket
     Banding(p,&t);
     return t;
 }


3. It's best not to do too many type conversions

One of the design goals of C++ rules is to ensure that type errors never occur. In theory, a program that compiles does not attempt to perform any unsafe, nonsensical operations on any body. Unfortunately, casting breaks the type system, which can cause any kind of trouble, some very trouble. Take the last code example in this article. Both C and C++ support invisible casting, and C++ has four display conversion operators. The choice between member and non-member functions is described. However, it is recommended not to do too much type conversion, to avoid it. Casting is not always what you want, so let's start with an example:


 #include <iostream> 
 class base
 {
     public:
         base():a(0),b(0){}
         base(const int& x,const int& y)
         :a(x),b(y){}
         virtual void init()
        {
            a=5;
            b=5;
            std::cout<<"in base a value is "<<a<<std::endl;
            std::cout<<"in base b value is "<<b<<std::endl;
        }

        int get_a() const
        {
            return a;
        }

        int get_b() const
        {
            return b;
        }
    private:
        int a;
        int b;
};
class derived:public base
{
    public:
        derived(int x,int y):base(x,y){}
        void init()
        {
            static_cast<base>(*this).init();
        }
};

The running result is
In base a value is 5
In base b value is 5
A value is 2
B value is 2

Here the derived type is converted to base, but calling the base::init() function is not a function on the current object, but a copy of "*this object's base created by the previous transformation action. So when we try to change the object's content, we actually change the copy's content, and the object's content is not changed.

How to solve this problem? We can declare functions that call the base class directly


class derived:public base
{
    public:
        derived(int x,int y):base(x,y){}
        void init()
        {
            //static_cast<base>(*this).init();
            base::init();
        }
};

The running result is:
In base a value is 5
In base b value is 5
A value is 5
B value is 5

Maybe at this point you should remember to use dynamic_case (if you've seen previous articles: it's used to safely cast down inheritance). An error occurred directly using dynamic_cast.


 class derived:public base
 {
     public:
         derived(int x,int y):base(x,y){}
         void init()
         {
             //static_cast<base>(*this).init();
             //base::init();
             dynamic_cast<base*>(this)->init();
         }
 };

The running result is:

Segment error ((main memory) information dump) suppose a class has five levels of single inheritance. If dynaic_cast is performed on this object, there are up to five STRCMP calls. The deeper or more multiple inheritance, the higher the cost. The reason why dynamic_cast is needed is because you want to perform the derived class action function on the derived class object, but currently there is only one pointer or reference to base, which can be handled with them.


Related articles: