C ++ flexible and error-prone features-02 and error-02
C ++ notes series: flexible and error-prone features-02
Keywords:
- Type and type conversion
- Typedef
- Scope
- Header file
- C-style tools
Type and type conversion typedef
typedef
Provide a new name for the existing type declaration. You can settypedef
For existing type declarationsSynonym.
typedef int* IntPtr
The new type name and its alias can be used interchangeably:
int* p1;IntPtr p2;
Fundamentally, they are of the same type:
// Completely valid p1 = p2; p2 = p2;
typedef
The most common usage is to provide a name that is easy to manage when the type declaration is too clumsy. Especially in the template.
If you don't want to bother writingstd::vector<std::string>
, You can usetypedef
,
typedef std::vector<std::string> StringVector;void ProcessVector(const StringVector& vec) {}int main {StringVector my_vector;return 0;}
In fact, STL is widely used.typedef
For examplestring
Is defined as follows:
typedef basic_string<char, char_traits<char>, allocator<char>> string
Function pointer typedef
Function pointers in C ++ are not common (virtual
Keyword substitution), but in some cases, you still need to obtain the function pointer. However, when defining function pointers,typedef
It is the most confusing.
Consider a dynamic link library (DLL) with the nameMyFunc()
. OnlyMyFunc()
To load the database.
Assume thatMyFunc()
The prototype is as follows,
int __stdcall MyFunc(bool b, int n, const char* p);
__stdcall
The Microsoft-specific command indicates how to pass parameters to functions and clean up.
Now you can usetypedef
Define an abbreviated name (MyFuncProc) for the pointer to the function, which has the prototype shown above:
typedef int (__stdcall *MyFuncProc)(bool b, int n, const char* p);
Note:typedef
NameMyFuncProc
It is quite confusing to embed it in this syntax. Another more concise method to solve this problem is described in the next section.Type alias.
Type alias
In some cases, the type alias Ratiotypedef
It is easier to understand.
typedef int MyInt;
You can use a type alias for writing.
using MyInt = int;
Whentypedef
Type aliases are especially useful when they become complex.
Typedef int (* FuncType) (char, double); // use the type alias using FuncType = int (*) (char, double );
Type aliasNot just easy to readtypedef
, Used in the templatetypedef
,typedef
The problem becomes very prominent.
For example, the following class template is available:
Template <typename T1, typename T2> class MyTemplateClass {}; // oktypedef MyTemplateClass <int, double> OtherName; // Errortypedef MyTemplateClass <T1, double> OtherName; // to do this, use the template alias template <typename T1> using OtherName = MyTemplateClass <T1, double>;
Type conversion
C ++ provides four types of conversions:static_cast
,dynamic_cast
,const_cast
,reinterpret_cast
. We strongly recommend that you use only C ++-style type conversion in the new code, which is safer and more syntactic than C-style()
Better.
1. const_cast
const_cast
It is the most direct and can be used for constant features of variables. This is unique among the four types of conversions.Discard constant featuresType conversion. Of course, theoretically, it is not necessaryconst
Type conversion. Variable isconst
, It should always beconst
.
However,Sometimes a function needsconst
Variable passed to non-const
Variable Function.The best way is to maintainconst
Consistency. In most cases, a third-party library is used. There is no way to construct a new program.
// Third-party external method extern void ThirdPartyLibraryMethod (char * str); void f (const char * str) {ThirdPartyLibraryMethod (const_cast <char *> (str ));}
2. static_cast
Displays the conversions directly supported by C ++. For example, in an arithmetic expressionint
Convertdouble
To avoid truncation, you can usestatic_cast
.
Int I = 3; int j = 4; // you only need to convert one of them to ensure that the floating point Division double result = static_cast <double> (I)/j;
You can also usestatic_cast
Execute the explicit conversion. For example, the constructor of Class A can use the object of Class B as A parameter.static_cast
Converts object B to object. This type of behavior is often required. Generally, the compiler automatically and implicitly executes this conversion.
static_cast
Another usage is to execute downward conversion in the class inheritance hierarchy.Note: it can only be used for pointer and reference of an object, and cannot be used to convert the object itself.
Note that,static_cast
Do not perform type detection during running. For example, the following code may cause disastrous consequences, including memory rewriting exceeding the boundaries of objects.
// Derived inherits from BaseBase * B = new Base (); Derived * d = static_cast <Derived *> (B );
You can usedynamic_cast
.
static_cast
It is not omnipotent.const
Type conversion to non-const type,A pointer of a certain type cannot be converted to another unrelated type.Cannot directly convert the object type. Basically, the type rule cannot be converted meaningless.
3. reinterpret_cast
reinterpret_cast
Ratiostatic_cast
More powerful and less secure. Can be usedThe execution is technically not allowed by rules, but in some cases type conversion is required.For example, two references can be converted to one another, even if the two are irrelevant. Similarly, even if the two pointers do not have an inheritance relationship, you can convert a pointer type to another pointer type.
reinterpret_cast
It is often used to convert a pointervoid*
Andvoid*
Convert to pointer.
Class X {}; class Y {}; int main () {X x; Y y Y; X * px = & x; y * py = & Y; // The conversion between unrelated pointer types can only be performed using reinterpret_castpx = reinterpret_cast <X *> (py); void * p = px; px = reinterpret_cast <X *> (p ); X & rx = x; Y & ry = reinterpret_cast <Y &> (x );}
Theoretically,reinterpret_cast
You can also convert the pointerint
, Orint
Convert to pointer. However, such programs may be incorrect. On many platforms (especially 64-bit), pointers andint
. For example, on a 64-bit platform, the pointer is 64-bit, And the integer may be 32-bit. Converting a 64-bit pointer to a 32-bit integer will result in the loss of 32-bit.
In additionreinterpret_cast
YesBe especially careful that it will not perform any type detection.
4. dynamic_cast
dynamic_cast
Provides runtime detection for type conversions within the inheritance hierarchy. It can be used to convert pointer or reference.dynamic_cast
Check the type information of the underlying object at runtime. If the type conversion is meaningless,dynamic_cast
Returns a null pointer (for pointers) or throwsstd::bad_cast
Exception (for reference ).
Note that the running type information is stored in the virtual table of the object. Therefore, to usedynamic_cast
,The class must have at least one virtual method.. If the class does not have a virtual table, usedynamic_cast
Compilation errors may occur.
The followingdynamic_cast
Will throw an exception:
# Include <iostream> using std: bad_cast; using std: cout; class Base {public: Base () {} virtual ~ Base () {}}; class Derived: public Base {public: Derived () {} virtual ~ Derived () {}}; int main () {Base base; Derived derived; // change it to Base & br = derived; no exception is thrown. Base & br = base; try {Derived & dr = dynamic_cast <Derived &> (br);} catch (const bad_cast &) {cout <"Bad cast! \ N ";}}
5. Summary of type conversion
Situation |
Type conversion |
Delete the const feature |
Const_cast |
Type conversion supported by the language (for example, int is converted to double, int is converted to bool) |
Static_cast |
Type conversion supported by user-defined constructors or conversion routines |
Static_cast |
Class Object to other (irrelevant) Class Object |
Unable to complete |
The pointer (pointer-to-object) of the Class object is converted to the pointer of other class objects in the same inheritance hierarchy. |
Static_cast or dynamic_cast (recommended) |
Convert a reference-to-object to a reference of another class object in the same inheritance hierarchy. |
Static_cast or dynamic_cast (recommended) |
Type pointer conversion (pointer-to-type) is another unrelated type pointer |
Reinterpret_cast |
Reference-to-type is a reference of another unrelated type. |
Reinterpret_cast |
The function pointer (pointer-to-function) is another function pointer. |
Reinterpret_cast |
Scope resolution
You can use the create scope
- Namespace
- Function Definition
- Block defined by curly brackets
- Class Definition
When trying to access a variable, function, or category, the name will be searched from the latest scope, and then adjacent scopes, and so on, until the global scope.
If you do not want to use the default scope to parse a name, you can use the scope resolution operator.::
And the specific scope. For example, for a static method of a method class, the first method is to place the class name (method scope) and scope parsing operator before the method name. The second method is to access the static method through the class object.
Header file
Header files provide abstract interfaces for code. When using header files, you must note that you do not need to reference them cyclically or include the same header file multiple times. The most common method is to use#ifndef
Mechanism.
#ifndef
It can be used to avoid loop inclusion and multiple inclusion. At the beginning of each header file,#ifndef
Indicates whether a tag is defined. If the tag has been defined, it indicates that the header file has been included.#endif
This command is generally at the end of the file. If the tag is not defined, the tag will be defined. Repeated inclusion will be ignored. This mechanism is also calledHeader file protection (include guards).
#ifndef LOGGER_H#define LOGGER_H#include "Preferences.h"class Logger {public:static void setPreferences(const Preferences& prefs);static void logError(const char* error);};#endif // LOGGER_H
If the compiler supports#pragma once
(Visual C ++ or g ++), which can be written as follows:
#pragma once#include "Preferences.h"class Logger {public:static void setPreferences(const Preferences& prefs);static void logError(const char* error);};
Forward declarations is another tool to avoid header file problems. If you need to use a class but cannot contain its header file (for example, this class is heavily dependent on the currently written class), you can tell the compiler that such a class exists, but it cannot be used.#include
. Of course, you cannot use this class in the Code. This named class exists only after the link is successful.
# Ifndef LOGGER_H # define LOGGER_Hclass Preference; // pre-declaration class Logger {public: static void setPreferences (const Preferences & prefs); static void logError (const char * error );};
In large projects, using a pre-declaration can reduce the compilation and re-Compilation Time, because it destroys the dependency of a header file on other header files. However, the pre-declaration also hides dependencies. When the header file is changed, your code will skip the necessary re-compilation process.
Pre-declaration may sometimes impede header file changes to APIs, especially functions, such as expanding the parameter type. In addition, general projects do not need to shorten the Compilation Time.
It is recommended that functions always use#include
, Class template priority#include
.
C Tool
C language has some obscure features that can be seen occasionally in C ++. For example, variable length argument lists and preprocessor macro (preprocessor macros ).
Variable Length Parameter List
In C language, this feature must be unique,printf()
This feature is used.
Note that the new Code should passvariadic
The template uses a type-safe variable length parameter list.
#include <cstdio>#include <cstdarg>bool debug = false;void DebugOut(const char* str, ...) {va_list ap;if (debug) {va_start(ap, str);vfprintf(stderr, str, ap);va_end(ap);}}int main(int argc, char const *argv[]) {debug = true;DebugOut("int %d\n", 5);DebugOut("String %s and int %d\n", "hello", 5);DebugOut("Many ints: %d, %d, %d, %d, %d\n", 1, 2, 3, 4, 5);return 0;}
Callva_start()
Must be called laterva_end()
Make sure that the stack is stable after the function is completed.
1. Access Parameters
You can useva_arg()
. However,If no explicit method is provided, the end of the parameter list cannot be known.
The first parameter can be used to calculate the number of parameters, or when the parameter is a set of pointers, the last parameter can be requirednullptr
.
In the following example, the caller needs to specify the number of parameters provided in the first named function.
void PrintInts(int num, ...) {int temp;va_list ap;va_start(ap, num);for (int i = 0; i < num; ++i) {temp = va_arg(ap, int);cout << temp << " ";}va_end(ap);cout << endl;}
2. Why not use the C-style variable-length parameter list?
- The number of parameters is unknown. For example
PrintInts()
You must trust the caller to pass a parameter with the same number as the first parameter.
- The parameter type is unknown.
Avoid using a variable-length parameter list in the C style as much as possible.array
,vector
Or the initialization list introduced by C ++ 11 is better. You can also usevariadic
The template uses a type-safe variable length parameter list.
Preprocessing macro
Use inline functions instead of macros. Macro errors are not performed. When macro is called, the preprocessing phase will automatically expand and replace, but the function call semantics will not be used. This line may cause unpredictable consequences. Also, macros are prone to errors. For example, there is a problem with writing like this:
#define SQUARE(x) (x * x)
Assume thatSQUARE(2 + 3)
.2 + 3 * 2 + 3
The calculation result is 11, not 25 of the expectation. The correct method is as follows:
#define SQUARE(x) ((x) * (x))
Note that the outermost parentheses cannot be omitted, otherwise they may be combined in arithmetic expressions due to priority issues.
If the calculation is complex, repeated expansion and replacement means that there will be a large overhead in repetitive operations.