B. Stroustrup - The C++ Programming Language (794319), страница 69
Текст из файла (страница 69)
Hardly any guarantees aremade for reinterpret_cast, but generally it produces a value of a new type that has the same bit pattern as its argument. If the target has at least as many bits as the original value, we can reinterpret_cast the result back to its original type and use it. The result of a reinterpret_cast is guaranteedto be usable only if its result is converted back to the exact original type. Note that reinterpret_castis the kind of conversion that must be used for pointers to functions (§12.5). Consider:char x = 'a';int∗ p1 = &x;int∗ p2 = static_cast<int∗>(&x);int∗ p3 = reinterpret_cast<int∗>(&x);// error : no implicit char* to int* conversion// error : no implicit char* to int* conversion// OK: on your head be itstruct B { /* ...
*/ };struct D : B { /* ... */ };// see §3.2.2 and §20.5.2B∗ pb = new D;D∗ pd = pb;D∗ pd = static_cast<D∗>(pb);// OK: implicit conversion from D* to B*// error : no implicit conversion from B* to D*// OKConversions among class pointers and among class reference types are discussed in §22.2.If you feel tempted to use an explicit type conversion, take the time to consider if it is reallynecessary. In C++, explicit type conversion is unnecessary in most cases when C needs it (§1.3.3)and also in many cases in which earlier versions of C++ needed it (§1.3.2, §44.2.3).
In many programs, explicit type conversion can be completely avoided; in others, its use can be localized to afew routines.11.5.3 C-Style CastFrom C, C++ inherited the notation (T)e, which performs any conversion that can be expressed as acombination of static_casts, reinterpret_casts, const_casts to make a value of type T from theexpression e (§44.2.3). Unfortunately, the C-style cast can also cast from a pointer to a class to apointer to a private base of that class.
Never do that, and hope for a warning from the compiler ifyou do it by mistake. This C-style cast is far more dangerous than the named conversion operatorsbecause the notation is harder to spot in a large program and the kind of conversion intended by theprogrammer is not explicit. That is, (T)e might be doing a portable conversion between relatedtypes, a nonportable conversion between unrelated types, or removing the const modifier from apointer type. Without knowing the exact types of T and e, you cannot tell.Section 11.5.4Function-Style Cast30311.5.4 Function-Style CastThe construction of a value of typeT(e).
For example:Tfrom a valueecan be expressed by the functional notationvoid f(double d){int i = int(d);// truncate dcomplex z = complex(d); // make a complex from d// ...}The T(e) construct is sometimes referred to as a function-style cast. Unfortunately, for a built-intype T, T(e) is equivalent to (T)e (§11.5.3). This implies that for many built-in types T(e) is not safe.void f(double d, char∗ p){int a = int(d); // truncatesint b = int(p); // not portable// ...}Even explicit conversion of a longer integer type to a shorter (such as long to char) can result innonportable implementation-defined behavior.Prefer T{v} conversions for well-behaved construction and the named casts (e.g., static_cast) forother conversions.11.6 Advice[1][2][3][4][5][6][7][8][9][10][11][12][13][14]Prefer prefix ++ over suffix ++; §11.1.4.Use resource handles to avoid leaks, premature deletion, and double deletion; §11.2.1.Don’t put objects on the free store if you don’t have to; prefer scoped variables; §11.2.1.Avoid ‘‘naked new’’ and ‘‘naked delete’’; §11.2.1.Use RAII; §11.2.1.Prefer a named function object to a lambda if the operation requires comments; §11.4.2.Prefer a named function object to a lambda if the operation is generally useful; §11.4.2.Keep lambdas short; §11.4.2.For maintainability and correctness, be careful about capture by reference; §11.4.3.1.Let the compiler deduce the return type of a lambda; §11.4.4.Use the T{e} notation for construction; §11.5.1.Avoid explicit type conversion (casts); §11.5.When explicit type conversion is necessary, prefer a named cast; §11.5.Consider using a run-time checked cast, such as narrow_cast<>(), for conversion betweennumeric types; §11.5.This page intentionally left blank12FunctionsDeath to all fanatics!– Paradox•••••••Function DeclarationsWhy Functions?; Parts of a Function Declaration; Function Definitions; Returning Values;inline Functions; constexpr Functions; [[noreturn]] Functions; Local VariablesArgument PassingReference Arguments; Array Arguments; List Arguments; Unspecified Number of Arguments; Default ArgumentsOverloaded FunctionsAutomatic Overload Resolution; Overloading and Return Type; Overloading and Scope;Resolution for Multiple Arguments; Manual Overload ResolutionPre- and PostconditionsPointer to FunctionMacrosConditional Compilation; Predefined Macros; PragmasAdvice12.1 Function DeclarationsThe main way of getting something done in a C++ program is to call a function to do it.
Defining afunction is the way you specify how an operation is to be done. A function cannot be called unlessit has been previously declared.A function declaration gives the name of the function, the type of the value returned (if any),and the number and types of the arguments that must be supplied in a call. For example:Elem∗ next_elem();void exit(int);double sqrt(double);// no argument; return an Elem*// int argument; return nothing// double argument; return a double306FunctionsChapter 12The semantics of argument passing are identical to the semantics of copy initialization (§16.2.6).Argument types are checked and implicit argument type conversion takes place when necessary.For example:double s2 = sqrt(2);double s3 = sqrt("three");// call sqrt() with the argument double{2}// error : sqr t() requires an argument of type doubleThe value of such checking and type conversion should not be underestimated.A function declaration may contain argument names.
This can be a help to the reader of a program, but unless the declaration is also a function definition, the compiler simply ignores suchnames. As a return type, void means that the function does not return a value (§6.2.7).The type of a function consists of the return type and the argument types. For class memberfunctions (§2.3.2, §16.2), the name of the class is also part of the function type.
For example:double f(int i, const Info&);char& String::operator[](int);// type: double(int,const Info&)// type: char& String::(int)12.1.1 Why Functions?There is a long and disreputable tradition of writing very long functions – hundreds of lines long. Ionce encountered a single (handwritten) function with more than 32,768 lines of code.
Writers ofsuch functions seem to fail to appreciate one of the primary purposes of functions: to break up complicated computations into meaningful chunks and name them. We want our code to be comprehensible, because that is the first step on the way to maintainability. The first step to comprehensibility is to break computational tasks into comprehensible chunks (represented as functions andclasses) and name those.
Such functions then provide the basic vocabulary of computation, just asthe types (built-in and user-defined) provide the basic vocabulary of data. The C++ standard algorithms (e.g., find, sort, and iota) provide a good start (Chapter 32). Next, we can compose functionsrepresenting common or specialized tasks into larger computations.The number of errors in code correlates strongly with the amount of code and the complexity ofthe code. Both problems can be addressed by using more and shorter functions.
Using a functionto do a specific task often saves us from writing a specific piece of code in the middle of other code;making it a function forces us to name the activity and document its dependencies. Also, functioncall and return saves us from using error-prone control structures, such as gotos (§9.6) and continues (§9.5.5).
Unless they are very regular in structure, nested loops are an avoidable source oferrors (e.g., use a dot product to express a matrix algorithm rather than nesting loops; §40.6).The most basic advice is to keep a function of a size so that you can look at it in total on ascreen. Bugs tend to creep in when we can view only part of an algorithm at a time. For many programmers that puts a limit of about 40 lines on a function. My ideal is a much smaller size still,maybe an average of 7 lines.In essentially all cases, the cost of a function call is not a significant factor.
Where that costcould be significant (e.g., for frequently used access functions, such as vector subscripting) inliningcan eliminate it (§12.1.5). Use functions as a structuring mechanism.Section 12.1.2Parts of a Function Declaration30712.1.2 Parts of a Function DeclarationIn addition to specifying a name, a set of arguments, and a return type, a function declaration cancontain a variety of specifiers and modifiers. In all we can have:• The name of the function; required• The argument list, which may be empty (); required• The return type, which may be void and which may be prefix or suffix (using auto); required• inline, indicating a desire to have function calls implemented by inlining the function body(§12.1.5)• constexpr, indicating that it should be possible to evaluate the function at compile time ifgiven constant expressions as arguments (§12.1.6)• noexcept, indicating that the function may not throw an exception (§13.5.1.1)• A linkage specification, for example, static (§15.2)• [[noreturn]], indicating that the function will not return using the normal call/return mechanism (§12.1.4)In addition, a member function may be specified as:• virtual, indicating that it can be overridden in a derived class (§20.3.2)• override, indicating that it must be overriding a virtual function from a base class (§20.3.4.1)• final, indicating that it cannot be overriden in a derived class (§20.3.4.2)• static, indicating that it is not associated with a particular object (§16.2.12)• const, indicating that it may not modify its object (§3.2.1.1, §16.2.9.1)If you feel inclined to give readers a headache, you may write something like:struct S {[[noreturn]] virtual inline auto f(const unsigned long int ∗const) −> void const noexcept;};12.1.3 Function DefinitionsEvery function that is called must be defined somewhere (once only; §15.2.3).
A function definition is a function declaration in which the body of the function is presented. For example:void swap(int∗, int∗);// a declarationvoid swap(int∗ p, int∗ q){int t = ∗p;∗p = ∗q;∗q = t;}// a definitionThe definition and all declarations for a function must specify the same type. Unfortunately, to preserve C compatibility, a const is ignored at the highest level of an argument type.