Depth operator overload

Depth operator overload (2)

1. When an operator is overloaded, when is it returned as a reference and when is it returned as a value?

Return as value:

 Int operator+(const Int& it)
 {
     this->value += it.value;
     
     return Int(this->value + it.value);
 }

Return by reference:

Int &operator+=(const Int &it)
{
    this->value += it.value;
    
    return *this;
}

a += b;

Summary: when an operator returns itself, it returns by reference. However, when returning a dead value, you need to use the return value.

2. Three functions of constructors

class Int
{
private:
	int value;
public:
    //1 2 3
    Int(int x = 0):value(x)
    {
        cout << "Int()" << this << endl;
    }
    
    Int(const Int &it):value(it.value) 
    {
        cout << "Copy Int:" << this << endl;
    }
    
    Int &operator=(const Int &it)
    {
        if(this != &it)
        {
            value = it.value;
        }
       	cout << this << " = " << &it << endl;
        
        return *this;
    }

}

int main()
{
    Int a = 10;			//Custom type
   	//Int a(10);
    
    int b = 100;		//Built in type
    
    a = b;
}

Constructor has three functions:

  1. Build object
  2. Initialize object
  3. Type conversion (single argument constructors only)

3. What is type conversion

Let's look at the following example

Question: as shown in the main function above, can b be assigned to a?

Answer: the third function of constructor is type conversion

First, we observe that one is the Int type and the other is the custom Int type. If you assign a value to b, it obviously can't pass, but the compiler doesn't report an error. What's the matter?

We convert b to a custom type. Then assign the converted type to a.

By calling, we can find:

We create a temporary object with the address 012FF690, and then assign the temporary object to object a.

Let's take another look at the deconstruction process:

~Int()
{
    cout << "Destroy Int: " << this << endl;
}


It can be found that the temporary object will be destroyed after assignment.

3.1 the constructor that can be type converted here must be single parameter.

//Constructor
Int(int x,int y):value()
{
    cout << "Int()" << this << endl;
}

int main()
{
    Int a(10,20);
    int b = 100;
    
    a = b;		//error: must contain only one parameter
}

If the initial value of the parameter is changed, it can also be regarded as a parameter:

Int(int x,int y = 0):value()
{
    cout << "Int()" << this << endl;
}

int main()
{
    Int a(10);
    int b = 100;
    
    a = b;				//right: the default value is assigned
}

Question: what is the value of a?

Int(int x,int y = 0):value(x + y)
{
    cout << "Int()" << this << endl;
}

int main()
{
    Int a(1,2);
    int b = 100;
    //a = b;
    //a = b,20; 		// Comma operator, whichever is the rightmost.
    
    //a = (Int)(b,20); 	// It is also a comma expression, taking 20, that is, (Int) 20;
    //a = Int(b,20); 	// Call the constructor of two parameters to create an unnamed object, give 20 to y and B to x, and the result is 120; 
}

Finally, both will generate temporary objects. The first method calls the constructor according to the type conversion method, and the second method generates an unnamed object and receives two parameters.

Next to the above question, if you give two parameters, why not give default initialization?

Int(int x,int y):value(x + y)
{
    cout << "Int()" << this << endl;
}

int main()
{
    Int a(10,20);
    int b = 100;
    
    a = (Int)(b,100);	//error: force conversion to create nameless object
    a = Int(b,100);		//right: call constructor to create nameless object

}

For the object whose type is forced to type, we can only give one valid parameter, and give more error

3.2 if implicit constructor is not allowed: add explicit keyword before constructor.

...
explicit Int(int x = 0):value(x)
{
    cout << "Int()" << this << endl;
}
...
    
int main()
{
    Int a(10);
    int b = 100;
    
    a = b;				//error, implicit type conversion is not allowed
    
    a = (Int) (200);	//right, you can
}

3.3 can we assign variables to objects?

explicit Int(int x):value(x)
{
    cout << "Create Int:" << this << endl;
}

int main()
{
    Int a(10);
    int b = 100;
    
    a = (Int)b;
    
    b = a;			//error
    
    b = (int)a;		//Attempting type conversion: error
    
}

You can't do this. We don't have a default function of cast type. We need to write it ourselves. The return type is the type of forced conversion (given by the system)

operator int()const 
{
    return value;	
    //The return type is a forced type
}

int operator int()const 
{
    return value;
}
//If this is the case, it happens that:
// b = (int)a;

//If this is the case: b = (float)a;
//It is impossible to write another overloaded function of type float
//The main function of the above code
int main()
{
    Int a(10);
    int b = 100;
    
    a = (Int)b;
    
    b = a;
    //Call strong conversion function
    //Equivalent to: b = a.operator int();
    //Equivalent to: B = operator int & A;
    b = (int)a;		
    
}

Summary:

  1. When we need to assign a built-in type to an object, we can construct an unnamed object through coercion and single parameter constructor;
  2. When you need to assign an object to a built-in type, you can overload the function through the operator.

3.4 mutable keyword and imitation function

There are the following categories:

class Add
{
   mutable int value;
   //even if
public:
    Add(int x = 0) : value(x) {}
  	
    int operator()(int a,int b) const 
    //Adding const value cannot be modified. You can add mutable keyword to value
    {
        value = a + b;
        return value;
    }
};

The main function is as follows:

int main()
{
    int a = 10,int b = 20, c = 0;
    Add add;
    
    c = add(a,b);	//functor 
}

The concept of imitative function: imitative function is the imitative function, which means that its function is similar to the function but not the real function. Imitative function is also called function object. In C + +, it calls the operator by overloading the function, that is, the () operator.

Question: how does the add object call the constructor?

add will not call the constructor, but the imitation function

c = add(a,b);		//We overloaded the bracket operator

//amount to:
c = add.operator()(a,b);

More complex situations:

int main()
{
    int a = 10, b = 20, c = 0;
    c = Add()(a,b);
}

The type name plus () calls the constructor to generate a value object and call itself.

Add the overloaded operators a, c, and b.

Summary:

  1. The member variable modified by mutable can be modified even if const modification is added to this pointer in the member method.
  2. Any operator overloaded with parentheses is called a functor. When you encounter an object (), you should be aware that it may be an object construction or a call to an imitation function.

4. Constructors use the difference between initializing lists and assigning values

Consider how the following code is output:

#include<iostream>
using namespace std;

class Int
{
private:
	int value;
public:
    Int(int x = 0):value(x)
    {
        cout << "Int()" << this << endl;
    }
    
    Int(const Int &it):value(it.value) 
    {
        cout << "Copy Int:" << this << endl;
    }
    
    Int &operator=(const Int &it)
    {
        if(this != &it)
        {
            value = it.value;
        }
       	cout << this << " = " << &it << endl;
        
        return *this;
    }
	
    ~Int()
    {
        cout << "Destory Int:" << this << endl;
    }
};


class Object
{
    int num;
    Int val;			//Int type object
public:
    Object(int x,int y)
    {
        num = x;
        val = y;

        cout << "create Objcet:" << this << endl;
    }

    ~Object()
    {
        cout << "destroy Object:"<< this << endl;
    }
};
int main()
{
    Object obj(1,2);
}

Process analysis:

Output result:

Initializing the list has the same effect as assigning values in the constructor (only for built-in types)

Object(int x,int y):num(x)	//Equivalent assignment statement
{
    cout << "Create Object:"<< this << endl;
    //num = x; 		// Equivalent to initialization list
    val = y;
}

However, custom types are different, and there is no need to construct temporary objects

Object(int x,int y):num(x),val(y)
{
    cout << "Create Object:"<< this << endl;
    //num = x; 		// Equivalent to initialization list
    //val = y;
}

Summary: for built-in types, the efficiency is the same; For the initialization of user-defined types, try to use the initialization list to save space.

5. Construction order of class data members

Build according to the design sequence

#include<iostream>
using namespace std;

class Object
{
    int num;
    int sum;		//Related to definition data
public:
    Object(int x = 0):sum(x),num(sum)
    {
        
    }
    
    void Print()const 
    {
        cout << num << " " << sum << endl;
    }
};

The construction of class data members in C + + has nothing to do with the order of members in the initialization list, and it is related to the data defined in the class.

Process analysis:

  1. First, num is constructed, followed by sum. According to the, the compiler will initialize all data members to 0 first and then assign values.
  2. Num is constructed first, and the value of sum is assigned to num. However, sum is not constructed at this time. It is 0 or a random number. This value is assigned to num.
  3. Then assign the value of x to sum.

6. Object lifetime management method

class Object
{
    Int *ip;
public:
    Object(Int *s = NULL):ip(s) {}
    ~Object()
    {
        if(ip != NULL)
        {
            delete ip;
        }
        ip = NULL;
    }
};


int main()
{
    Object obj(new Int(10));
    //Three functions of new
    //1. Apply for space from the stack area
    //2. Call the constructor to construct the object
    //3. Returns the address of the object
}

Process analysis:

  1. new has three functions. First, it will open up a space from the heap area, then call the constructor to build the object, and finally return the address of the object. Store 10 in the open space, point to the space with the pointer s, and assign s to ip in the constructor.
  2. After the end of the object, if the ip is empty, delete the ip. Assign ip to null.

What are the benefits of doing so?

The lifetime of Int objects can be automatically managed. We compare the upper and lower codes.

Manual management:

int fun()
{
    Int *ip = new Int(10);
    
    ...
        
    delete ip;		//If there is no such step, a memory leak will occur and we will do it manually
}

int main()
{
    fun();
}

Automatic management:

int fun()
{
    Object obj(new Int(10));
    //In the space requested in the heap area, if the fun function ends, the obj local object will be destroyed and its destructor will be called. Its destructor contains delete, which is automatically performed by the system
}

int main()
{
    fun();
}

7. * the difference between the operator and the object returned after the - > operator is overloaded

Analyze the following code:

class Int
{
private:
	int value;
public:
    Int(int x = 0):value(x)
    {
        cout << "Int()" << this << endl;
    }
    
    Int(const Int &it):value(it.value) 
    {
        cout << "Copy Int:" << this << endl;
    }
    
    Int &operator=(const Int &it)
    {
        if(this != &it)
        {
            value = it.value;
        }
       	cout << this << " = " << &it << endl;
        
        return *this;
    }
	
    ~Int()
    {
        cout << "Destory Int:" << this << endl;
    }

    int &Value()
    {
        return value;
    }

    const int &Value()const 
    {
        return value;
    }

};

class Object
{
    Int *ip;
public:
    Object(Int *s = NULL):ip(s) {}
    ~Object()
    {
        if(ip != NULL)
        {
            delete ip;
        }
        ip = NULL;
    }

    Int &operator*()
    {
        return *ip;
    }

    const Int& operator*() const  
    {
        return *ip;
    }

    Int *operator->()
    {
        return ip;
    }

    const Int* operator->() const
    {
        return ip;
    }
};

int main()
{
    Object obj(new Int(10));
    Int *ip = new Int(10);
    (*ip).Value();
    (*obj).Value();
    obj->Value();
    ip->Value();
}

Overload * returns the object itself, and overload pointer returns the point of the object.

Why? Let's look at the concept of combination:

Combination:

  1. Value type
  2. Pointer type
  3. Reference type (most complex)

The first combination is the most common. The val Object is a part of the whole Object class.

The second combination of pointer types. ip is not a part of the Object class. It is a weak association. The Object pointed to by the pointer can be changed.

If ip access is used, the object itself is returned. If you access through * - >, you will get the address * * of the heap area.

Third, the combination modifies the objects in the class by reference, which is a strong association. Modifying the val object will affect.

After understanding this, we can further modify - > as follows:

Int * opearotr->()	//Overload - > operator
{
    return &**this;
    //return ip; 			// Returns the address of the object
}

const Int *opearotr->()const 
{
    return &**this;
    //return ip;
}

Question: & * * how is this understood?

This pointer points to the current object, * this means the object itself.

Another one is * * this, which will call the overloaded function and return the alias of the object pointed to (return * ip).

At this time, adding &, and dereferencing cancel each other, which is the ip address.

So, return to the original code, why dereference plus Can it have the same effect as - > do?

class Int
{
private:
	int value;
public:
    Int(int x = 0):value(x)
    {
        cout << "Int()" << this << endl;
    }
    
    Int(const Int &it):value(it.value) 
    {
        cout << "Copy Int:" << this << endl;
    }
    
    Int &operator=(const Int &it)
    {
        if(this != &it)
        {
            value = it.value;
        }
       	cout << this << " = " << &it << endl;
        
        return *this;
    }
	
    ~Int()
    {
        cout << "Destory Int:" << this << endl;
    }

    int &Value()
    {
        return value;
    }

    const int &Value()const 
    {
        return value;
    }

};

class Object
{
    Int *ip;
public:
    Object(Int *s = NULL):ip(s) {}
    ~Object()
    {
        if(ip != NULL)
        {
            delete ip;
        }
        ip = NULL;
    }

    Int &operator*()
    {
        return *ip;
    }

    const Int& operator*() const  
    {
        return *ip;
    }

    Int *operator->()
    {
        return ip;
    }

    const Int* operator->() const
    {
        return ip;
    }
};

int main()
{
    Object obj(new Int(10));
    Int *ip = new Int(10);
    (*ip).Value();
    (*obj).Value();
    obj->Value();
    ip->Value();
}

Before creating objects, space is allocated for obj objects and p objects. There are pointer objects in obj object, so it takes up 4 bytes (32 bits); p object is a traditional pointer object, which also occupies 4 bytes.

When the obj object is to be constructed, its temporary object parameters will be constructed first, and a space will be created in the heap area to save the nameless object, with a value of 10. Next, the p object also needs to create an unnamed object, which is directly pointed to by p with a value of 1.

Then p accesses the content of the space caused by p; * obj needs to call the overloaded function of the operator and return the dereferenced ip, that is, the temporary object with the Value of 10. The temporary object gets its Value through Value.

P - > value() has nothing to say. The key lies in obj - > value (), which first calls the - > overloaded operator* Obj means to get ip, * * obj means * ip, to get the contents of temporary objects* ip, & and * cancel each other, which is ip. ip - > value(), similar to the most common p - > value().

Keywords: C++ Back-end

Added by phpPete on Sun, 13 Feb 2022 16:29:46 +0200