C + + conversion constructor: converts other types to the type of the current class

Posted by kida462 on Sun, 13 Feb 2022 02:39:00 +0100

C + + conversion constructor: converts other types to the type of the current class

In C/C + +, different data types can be converted to each other. It is called automatic type conversion (implicit type conversion) if the user does not need to indicate how to convert, and it is called forced type conversion if the user needs to explicitly indicate how to convert.

Example of automatic type conversion:

int a = 6;
a = 7.5 + a;

The compiler treats 7.5 as double type. When solving the expression, first convert a to double type, and then add it to 7.5 to get the sum of 13.5. When assigning a value to an integer variable a, 13.5 is converted to an integer 13 and then assigned to a. In the whole process, we didn't tell the compiler how to do it. The compiler uses built-in rules to complete the conversion of data types.

Cast example:

int n = 100;
int *p1 = &n;
float *p2 = (float*)p1;

p1 is an int * type. It points to an integer stored in the memory. p2 is a float * type. After p1 is assigned to p2, p2 also points to this memory and processes the data in this memory as a decimal. We know that the storage formats of integers and decimals are quite different. It is absurd to treat integers as decimals, which may lead to inexplicable errors. Therefore, the compiler does not allow p1 to be assigned to p2 by default. However, after using cast, the compiler thinks that we know the risk and make appropriate trade-offs, so we finally allow this behavior.

Whether it is automatic type conversion or forced type conversion, the premise must be that the compiler knows how to convert. For example, converting a decimal to an integer will erase the number after the decimal point, and converting int * to float * simply copies the value of the pointer. These rules are built-in by the compiler, and we have not told the compiler.

In other words, if the compiler does not know the conversion rules, it cannot convert, and it is useless to use mandatory types. See the following example:

#include <iostream>
using namespace std;
//Plural class
class Complex{
public:
    Complex(): m_real(0.0), m_imag(0.0){ }
    Complex(double real, double imag): m_real(real), m_imag(imag){ }
public:
    friend ostream & operator<<(ostream &out, Complex &c);  //friend function
private:
    double m_real;  //real part
    double m_imag;  //imaginary part
};
//Overload > > operator
ostream & operator<<(ostream &out, Complex &c){
    out << c.m_real <<" + "<< c.m_imag <<"i";;
    return out;
}
int main(){
    Complex a(10.0, 20.0);
    a = (Complex)25.5;  //Error, conversion failed
    return 0;
}

25.5 is a real number and a is a complex number. After assigning 25.5 to a, we expect the real part of a to become 25.5 and the imaginary part to be 0. However, the compiler does not know the conversion rule, which is beyond the processing capacity of the compiler, so the conversion fails, and it is useless even with cast.

Fortunately, C + + allows us to customize the type conversion rules. Users can convert other types to the current class type or the current class type to other types. This custom type conversion rule can only appear in the form of class member functions. In other words, this conversion rule is only applicable to classes.

In this section, we will first explain how to convert other types to the current class type, and then explain how to convert the current class type to other types in the next section.

Conversion constructor

Converting other types to the current class type requires the help of a Conversion constructor. The Conversion constructor is also a kind of constructor, which follows the general rules of constructors. The Conversion constructor has only one argument.

Still taking the Complex class as an example, we add a conversion constructor to it:

#include <iostream>
using namespace std;
//Plural class
class Complex{
public:
    Complex(): m_real(0.0), m_imag(0.0){ }
    Complex(double real, double imag): m_real(real), m_imag(imag){ }
    Complex(double real): m_real(real), m_imag(0.0){ }  //Conversion constructor 
public:
    friend ostream & operator<<(ostream &out, Complex &c);  //friend function
private:
    double m_real;  //real part
    double m_imag;  //imaginary part
};
//Overload > > operator
ostream & operator<<(ostream &out, Complex &c){
    out << c.m_real <<" + "<< c.m_imag <<"i";;
    return out;
}
int main(){
    Complex a(10.0, 20.0);
    cout<<a<<endl;
    a = 25.5;  //Constructor conversion
    cout<<a<<endl;
    return 0;
}

Operation results:

10 + 20i
25.5 + 0i

Complex(double real); Is the conversion constructor. Its function is to convert the parameter real of double type into the object of complex class, and take real as the real part of complex number and 0 as the imaginary part of complex number. As a result, a = 25.5; The overall effect is equivalent to:

a.Complex(25.5);

The procedure of assignment is converted into the procedure of function call.

During mathematical operation, assignment, copy and other operations, if the type is incompatible and the double type needs to be converted to Complex type, the compiler will retrieve whether the current class defines the conversion constructor. If not, the conversion fails. If so, the conversion constructor will be called.

The conversion constructor is also a kind of constructor. In addition to converting other types to the current class type, it can also be used to initialize objects. This is the original meaning of the constructor. The following is the correct way to create objects:

Complex c1(26.4);  //Create a named object
Complex c2 = 240.3;  //The object is initialized by copying
Complex(15.9);  //Create anonymous object
c1 = Complex(46.9);  //Create an anonymous object and assign it to c1

When initializing an object by copying, the compiler first calls the conversion constructor to convert 240.3 to Complex type (create an anonymous object of Complex class), and then copies it to c2.

If the + operator has been overloaded to add two Complex objects, the following statement is also correct:

Complex c1(15.6, 89.9);
Complex c2;
c2 = c1 + 29.6;
cout<<c2<<endl;

When performing the addition operator, the compiler first converts 29.6 to the Complex type (creates an anonymous object of the Complex class) and then adds it.

It should be noted that in order to obtain the target type, the compiler will "do whatever it takes", will comprehensively use the built-in conversion rules and user-defined conversion rules, and will carry out multi-level type conversion, for example:
The compiler will first convert int to double according to built-in rules, and then convert double to Complex according to user-defined rules (int -- > double -- > Complex);

The compiler will first convert char to int according to built-in rules, then convert int to double, and finally convert double to Complex according to user-defined rules (char -- > int -- > double -- > Complex).

From this example, as long as a type can be converted to double type, it can be converted to Complex type. Take the following example:

int main(){
    Complex c1 = 100;  //int --> double --> Complex
    cout<<c1<<endl;
    c1 = 'A';  //char --> int --> double --> Complex
    cout<<c1<<endl;
    c1 = true;  //bool --> int --> double --> Complex
    cout<<c1<<endl;
    Complex c2(25.8, 0.7);
    //Suppose you have overloaded the + operator
    c1 = c2 + 'H' + true + 15;  //Convert char, bool and int to Complex type for further operation
    cout<<c1<<endl;
    return 0;
}

Operation results:

100 + 0i
65 + 0i
1 + 0i
113.8 + 0.7i

On constructor

The original intention of the constructor is to initialize the object when creating the object. The compiler will match different (overloaded) constructors according to the passed arguments. Reviewing the previous chapters, we have learned the following constructors so far.

  1. Default constructor. Is the constructor automatically generated by the compiler. Take the Complex class as an example. Its prototype is:
Complex();  //No parameters
  1. Normal constructor. Is a user-defined constructor. Take the Complex class as an example. Its prototype is:
Complex(double real, double imag);  //Two parameters
  1. Copy constructor. Called when the object is initialized by copying. Take the Complex class as an example. Its prototype is:
Complex(const Complex &c);
  1. Conversion constructor. Called when converting another type to the current class type. Take Complex as an example. Its prototype is:
Complex(double real);

No matter what kind of constructor, it can be used to initialize objects, which is the original intention of the constructor. Assuming that the Complex class defines all the above constructors, the following methods of creating objects are correct:

Complex c1();  //Call Complex()
Complex c2(10, 20);  //Call Complex(double real, double imag)
Complex c3(c2);  //Call complex (const complex & C)
Complex c4(25.7);  //Call Complex(double real)

These codes reflect the original intention of the constructor - initializing the object when it is created.

In addition to initializing the object when creating the object, the constructor will also be called in other cases. For example, the copy constructor will be called when initializing the object in the form of copy, and the conversion constructor will be called when converting other types to the current class type. These constructors that are called in other cases become special constructors. A special constructor does not necessarily reflect the original intention of the constructor.

Further refinement of the Complex class

In the above Complex class, we have defined three constructors, including two ordinary constructors and a conversion constructor. In fact, with the help of the default parameters of the function, we can simplify the three constructors into one. Please see the following code:

#include <iostream>
using namespace std;
//Plural class
class Complex{
public:
    Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
public:
    friend ostream & operator<<(ostream &out, Complex &c);  //friend function
private:
    double m_real;  //real part
    double m_imag;  //imaginary part
};
//Overload > > operator
ostream & operator<<(ostream &out, Complex &c){
    out << c.m_real <<" + "<< c.m_imag <<"i";;
    return out;
}
int main(){
    Complex a(10.0, 20.0);  //Pass 2 arguments to the constructor without using the default parameters
    Complex b(89.5);  //Pass 1 argument to the constructor and use 1 default parameter
    Complex c;  //Do not pass arguments to the constructor, use all default parameters
    a = 25.5;  //Call the transformation constructor (pass 1 argument to the constructor and use 1 default parameter)
    return 0;
}

The simplified constructor contains two default parameters. When calling it, some or all of the arguments can be omitted, that is, 0, 1 and 2 arguments can be passed to it. The conversion constructor is a constructor containing one parameter, which can be "fused" with the other two ordinary constructors.

Topics: C++ Back-end