C ++ Operator overloading, function objects, and class type conversion

Source: Internet
Author: User
Tags bitwise operators
This article mainly discusses Operator overloading, function objects, and class type conversion in C ++.

If there are any errors or omissions in this article, please point out. Thank you! The overload operator is a function with a special name: the reserved word operator is followed by the operator symbol to be defined.

Most operators can be reloaded, but several operators cannot be reloaded. There are four operators that cannot be overloaded:

:: . .* ? :

In addition, the existing operators in C ++ can only be overloaded, and new operators cannot be created by connecting other valid symbols. For example, defining an operator ** to increase the power of supply and demand is illegal.

The overload operator must have at least one class type or enumeration type operand, that is, the overload operator cannot redefine the meaning of the Operator Used for built-in type objects. The operator priority, associativity, or number of operands cannot be changed. There are four symbols (+,-, *, &) that can be used as both the unary operator and the binary operator. In addition to the function call operator (), it is invalid to use the default real parameter when the operator is overloaded. The overloaded operators do not guarantee the calculation sequence of the operands, especially the calculation sequence of the operands of the built-in logic AND, logical OR, AND comma operators. In the overload version of & |, both operands require calculation, instead of following the short-circuit calculation rule. The Design of overload operators should not overload operator value assignment operators with built-in meanings, address-taking operators, or comma operators, which have default meanings for class-type operands. If no specific version is overloaded, the compiler automatically defines the following operators:> merge assignment operators: assign values to members one by one and use their own assignment operators. > By default, the bitwise operators (,) and comma (,) are executed on class objects, the same as those on built-in class objects. The address operator returns the memory address of the object. The comma operator calculates the value of each expression from left to right and returns the value of the rightmost operand. Note: If you overload your version of the comma operator, the calculation order of the operands is no longer guaranteed. > The built-in logic and (&) and logic or (|) operators use short-circuit calculation rules. If the operator is redefined, the short-circuit computation feature of the operator will be lost.
Note: The comma operator (,), the address operator (&), the logic and operator (&), and the logic or operator (|) have built-in meanings, they should not be reloaded normally. This is because the overload does not have these built-in meanings.
Equal and Relational operators in the associated container, the key type should be defined <operator, because the associated volume uses the key type <operator by default; In the ordered container, it should usually be defined equal (=) operators and less than (<) operators, because many algorithms in the standard library use these operators by default. For example, the sort algorithm uses the <operator, while the find algorithm uses the = Operator. If the class defines the equality operator, it should also define the inequality operator! =. Because class users generally assume that they can perform equal comparison, they can also perform unequal comparison. This rule is also used for other Relational operators. Of course ,! = Is usually implemented by =, while>, <=,> = is also implemented by <. The following rules apply to the selection of a member function or a non-member function (youyuan):> value assignment (=), subscript ([]), and call (()) and the member access arrow (->) and other operators must be defined as members. defining these operators as non-member functions will be recorded as errors during compilation. > Like assignment, composite assignment operators should generally be defined as class members. Unlike assignment, this is not necessarily the case. If you define a non-member compound assignment operator, no compilation error will occur. > Changes the object state or other operators closely related to the given type, such as auto-increment, auto-subtraction, and reference, should generally be defined as class members. > Symmetric operators, such as arithmetic operators, equal operators, Relational operators, and bit operators, should be defined as common non-member functions.

Implementation output operators of overload operators <generally, output operators should output the content of objects for a minimum formatting. They should not output line breaks. In this way, the user can control the output details and give the user more choices. The I/O operator should be a non-member function to implement the same behavior as the standard library I/O. Input operator> the input operator should check possible errors in the input process, such as Type Mismatch Errors and file Terminator. If a stream error occurs during the input process, ensure that the object is in the available and consistent state. You can check the stream status after the input to determine whether the input is normal. If the input is normal, however, if the data does not meet certain restrictions, you can set the stream status flag to failbit to indicate that the input fails. To maintain consistency with built-in operators, arithmetic operators (such as addition) should return a right value instead of a reference. That is, the class that defines Arithmetic Operators and corresponding compound value assignment operators. Generally, compound value assignment should be used to implement arithmetic operators. For example, operator + is implemented by operator + =. The value assignment operator must be defined as a member function, and the value assignment operator and the compound value assignment operator should return a reference to the left operand (* this is returned ). The subscript operator must be defined as a class member function. The result is generally used as the left and right operands of the value assignment operator. Therefore, two versions are generally defined: one non-const member and the other returns a reference. The other is a const member and returns a const reference. Member access operators member access operators include the unreferenced operators (*) and arrow operators (-> ). The arrow operator must be defined as a class member function. The unreferenced operator does not need to be defined as a member, but it is generally correct to use it as a member. The member access operator is the same as the subscript operator. Generally, the const version and the non-const version should be defined. The arrow operator is special because it acts like a binary operator: it accepts an object and a member name, but in fact it does not accept explicit parameters. For example, Ptr * operator-> () {return ptr ;}. There is no form parameter here, because the right operand of-> is not an expression, it is an identifier corresponding to a class member. There is no obvious reliable way to pass an identifier as a form parameter to a function. Instead, the compiler processes the work of getting members. When writing a program: ptr-> action ();, it is equivalent to :( ptr-> action )();. The compiler evaluates ptr-> action according to the following rules: 1) If ptr is a pointer, it points to a class object with a member named action, the compiler compiles the Code as an action Member that calls the object. 2) Otherwise, if action is an object that defines the operator-> operator class, ptr-> action is the same as ptr. operator-> ()-> action. That is, execute the operator-> () of the ptr, and then use the result to repeat these three steps. 3) otherwise, the Code fails. It can be seen that the arrow operator must return a pointer to the class type, or return a class object that defines its own arrow operator. In general, we define the auto-increment and auto-increment operators as member functions of the class. Both operators have two forms: prefix and suffix. The auto-increment and auto-increment operators in prefix form are the same as those defined in common operators. To maintain consistency with built-in types, prefix auto-increment and auto-increment operators should return object references. To distinguish between prefix and suffix, the auto-increment and auto-increment operators in the suffix form accept an additional int-type parameter. When an operator in the form of a suffix is used, the compiler provides 0 as the real parameter of this form parameter. This form parameter cannot be named during the implementation of the operator. To be consistent with the built-in operator, the suffix operator should return the old value and return the value instead of the reference. Generally, the extension form is implemented by calling the prefix form. Example: Demo & Demo: operator ++ () {// prefix form // do something here return * this;} Demo: operator ++ (int) {// postfix form Demo ret (* this); ++ * this; return ret;} if you need to call the suffix operator in the form of a function explicitly, an integer real parameter value should be given. Example: obj. operator ++ (); // call prefix operator ++ obj. operator ++ (0); // call postfix operator ++ call operator can be a class-type object overload function call operator. The function call operator must be declared as a member function. The form of the function is somewhat strange (see the example below ). For example, the following structure encapsulates an operation to calculate the absolute value: struct AbsInt {int operator () (int val) {return val <0? -Val: val ;}}; int I =-10; AbsInt absObj; unsigned int ui = absOjb (I );

Function objects and their function adapters define classes that call operators. Their objects are often called function objects. That is, their behaviors are similar to those of functions. Function objects are usually used as real parameters of generic algorithms. For example, a large number of generic algorithms in the standard library have versions that require function objects. Function objects can be used more flexibly than functions. The standard library defines a set of arithmetic, relational, and logical function object classes in the header file <functional>. It also defines a set of function adapters, this allows us to customize or extend the function object classes defined in the standard library.
Arithmetic function object type Plus <Type> +
  Minus <Type> -
  Multiplies <Type> *
  Divides <Type> /
  Modulus <Type> %
  Negate <Type> -
Relational function object type Performance_to <Type> =
  Not_0000_to <Type> ! =
  Greater <Type> >
  Greater_equal <Type> > =
  Less <Type> <
  Less_equal <Type> <=
Logical function object type Logical_and <Type> &&
  Logical_or <Type> |
  Logical_not <Type> !
Each standard library function object class represents an operator, that is, each class defines the call operator that applies the naming operation. There are only two unary function objects: negate <Type> and <logical_not <Type>. The others are binary function objects ). Function objects are often used to override the default operators used in algorithms. When using function objects in the standard library, you need to generate an Instance Object of the template class, and then pass the generated function object to the algorithm. For example, sort uses operator <to sort containers in ascending order by default. To sort containers in descending order, you can pass the function object greater: sort (svec. begin (), svec. end (), greater <string> (); where, greater <string> () indicates to generate a greater function object whose type is string. The standard library provides a set of function adapters for special and extension of mona1 and binary function objects. Function adapters are divided into the following two types: 1) binder: it binds an operand to a given value and converts a binary function object to a one-dimensional function object. 2) negator: it returns the true value of the predicate function object. The standard library defines two biner adapters: bind1st and bind2nd. Each binder accepts a function object and a value. Bind1st binds a value to the first real parameter of the binary function object, and bind2nd binds the value to the second real parameter of the binary function object. For example, to calculate the number of all elements smaller than or equal to 10 in a container, you can pass the count_if value: count_if (vec. begin (), vec. end (), bind2nd (less_equal <int> (), 10); the standard library also defines two backend servers: not1 and not2. Not1 returns the true value of the mona1 function object, while not2 returns the true value of the binary function object. Example: count_if (vec. begin (), vec. end (), not1 (bind2nd (less_equal <int> (), 10); the result is to count those elements that do not <= 10, that is, the number of elements greater than 10 is counted. We already know why class type conversion is required for class type conversion. For Single-parameter constructors without explicit it statements, automatic conversion from parameter type to class type can be implemented. C ++ also provides such a mechanism: a class can define its own conversion to other types. If we need to define a class and a built-in int type for mixed arithmetic and relational operations, and considering the order of the left and right operands can be exchanged, to design this class, we need to define a large number of operators. In this case, we can define a conversion from the class to the int type. If this conversion is defined, the object can be applied to any places that can be used for the int type without defining any arithmetic or Relational operators. Conversion operator is a special class member function in the form of operator type, type indicates the built-in type name, class type name, or name defined by the type alias. You can define conversion functions for any type that can be returned as a function (except void. Generally, it is not allowed to be converted to an array or function type. It is acceptable to convert to the pointer type (data and function pointer) and reference type. The conversion function must be a member function. The return type cannot be specified, and the parameter table must be empty. Although the conversion function cannot specify the return type (because the return type is already specified in the conversion target type), each conversion function must explicitly return a value of the specified type. Generally, the conversion operator should be defined as a const member. If class type conversion exists, the compiler will automatically call it where the conversion can be used. When using the conversion function, the converted type does not have to match the required type. If necessary, follow the standard conversion after class type conversion to obtain the desired type. For example, if the SmallInt class defines the conversion to int, SmallInt si; double dval; si> = dval // si converted to int and then convert to double if (si) // si converted to int and then convert to bool int func (int); func (si ); // si converted to int and then call func // instruct compiler to cast si to int ival = static_cast <int> (si) + 3; only one class type conversion can be applied: after the class type conversion, you cannot use another class type conversion to obtain the required type. Only one class type conversion is allowed. Otherwise, an error occurs. The ambiguous problem may cause ambiguity during real-time Parameter Matching and conversion for poorly designed programs. Generally, when multiple candidates match, the compiler selects the best match. In addition, when there is a problem of ambiguity, it is generally possible to eliminate the ambiguity through forced type conversion (although this is a poor performance of program design ). However, in some cases, the compiler will still mark the situations that seem to be differentiated as errors, and in some cases, the problem of ambiguity cannot be eliminated through forced conversions. Scenario 1: the compiler will not try to differentiate two different class types for conversion. Specifically, the compiler will mark the call as an error even if a call needs to be converted to a standard call after the class type conversion and the other is a perfect match. For example:
Void compute (int );
Void compute (long double );
Class SmallInt
Operator int () const {return val ;}
Operator double () const {return val ;}
Std: size_t val;
}; SmallInt si; compute (si); // error: ambiguous
Of course, this ambiguity can be eliminated through forced conversion. For example: compute (static_cast <int> (si); Scenario 2: When multiple classes have single-parameter constructors of the same type, this may lead to ambiguity. In this case, even if one version requires a standard conversion, and the other version does not, the compiler will consider it to be meaningless. For example:
Class SmallInt {
SmallInt (int );
Class Integral {
Integral (short );
Void manip (const Integral &);
Void manip (const SmallInt &);
Manip (10); // error: ambiguous
In this case, you can call the constructor explicitly to eliminate the ambiguity: manip (SmallInt (10); Scenario 3: When two Classes define mutual conversion, there may be ambiguity. For example:
Class Integral;
Class SmallInt {
SmallInt (Integral );
Class Integral {
Operator SmallInt () const;
Void compute (SmallInt); Integral val; compute (val); // error: ambigugous
The above example is from "C ++ Primer, Chinese E4, P460". The book says that in this case, the problem of ambiguity cannot be eliminated by display conversions, because Explicit conversions can both use conversion operators and constructors. In this case, you need to explicitly call the conversion operator or constructor: compute (val. operator SmallInt (); // OK: use conversion operator compute (SmallInt (val); // OK: use SmallInt constructor but in fact, I am in MinGW2.05 (GCC3.4.2) there is no ambiguity error in compilation in and GCC4.1. Both compilers give priority to calling constructors to complete the conversion. No relevant information is found. Mark it first. The reload validation of overload, conversion, and operator operators follows three common steps: 1) select a candidate function; 2) Select a viable function, including identifying the potential conversion sequence of each real parameter; 3) select the best matching function. Generally, the candidate function set consists of all functions with the same name as the used function. The used function can be seen from the function call. When an operator is used in an expression, the candidate function includes the built-in version of the operator and the normal non-member version of the operator. In addition, if the left operand has a class type and the class defines the overloaded version of the operator, the candidate set contains the overloaded version of the operator. Be careful when designing heavy-duty operators, conversion functions, and conversion constructors of classes. In particular, classes that define both conversion operators and overload operators are prone to ambiguity. The following are some empirical rules for reference: 1) do not define classes for mutual conversion, that is, if the class Foo has the constructor that accepts the objects of the class Bar, do not define conversion operators for the class Bar to the type Foo. 2) avoid conversion to built-in arithmetic types. Specifically, if the conversion to the arithmetic type is defined, do not define an overloaded version that accepts Arithmetic Operators. If you need to use these operators, the conversion operators will convert the objects of the type you define, and then you can use the built-in operators.> Do not define conversions to more than one arithmetic type. Provides standard conversions to other arithmetic types. If there are any errors or omissions in this article, please point out. Thank you!
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.