B. Stroustrup - The C++ Programming Language (794319), страница 72
Текст из файла (страница 72)
The most obvious use of rvalue references is to define moveconstructors and move assignments (§3.3.2, §17.5.2). I’m sure someone will find a clever use forconst-rvalue-reference arguments, but so far, I have not seen a genuine use case.Please note that for a template argument T, the template argument type deduction rules give T&&a significantly different meaning from X&& for a type X (§23.5.2.1).
For template arguments, anrvalue reference is most often used to implement ‘‘perfect forwarding’’ (§23.5.2.1, §28.6.3).318FunctionsChapter 12How do we choose among the ways of passing arguments? My rules of thumb are:[1] Use pass-by-value for small objects.[2] Use pass-by-const-reference to pass large values that you don’t need to modify.[3] Return a result as a return value rather than modifying an object through an argument.[4] Use rvalue references to implement move (§3.3.2, §17.5.2) and forwarding (§23.5.2.1).[5] Pass a pointer if ‘‘no object’’ is a valid alternative (and represent ‘‘no object’’ by nullptr).[6] Use pass-by-reference only if you have to.The ‘‘when you have to’’ in the last rule of thumb refers to the observation that passing pointers isoften a less obscure mechanism for dealing with objects that need modification (§7.7.1, §7.7.4)than using references.12.2.2 Array ArgumentsIf an array is used as a function argument, a pointer to its initial element is passed.
For example:int strlen(const char∗);void f(){char v[] = "Annemarie";int i = strlen(v);int j = strlen("Nicholas");}That is, an argument of type T[] will be converted to a T∗ when passed as an argument. This impliesthat an assignment to an element of an array argument changes the value of an element of the argument array. In other words, arrays differ from other types in that an array is not passed by value.Instead, a pointer is passed (by value).A parameter of array type is equivalent to a parameter of pointer type. For example:void odd(int∗ p);void odd(int a[]);void odd(int buf[1020]);These three declarations are equivalent and declare the same function. As usual, the argumentnames do not affect the type of the function (§12.1.3).
The rules and techniques for passing multidimensional arrays can be found in §7.4.3.The size of an array is not available to the called function. This is a major source of errors, butthere are several ways of circumventing this problem. C-style strings are zero-terminated, so theirsize can be computed (e.g., by a potentially expensive call of strlen(); §43.4). For other arrays, asecond argument specifying the size can be passed.
For example:void compute1(int∗ vec_ptr, int vec_size);// one wayAt best, this is a workaround. It is usually preferable to pass a reference to some container, such asvector (§4.4.1, §31.4), array (§34.2.1), or map (§4.4.3, §31.4.3).If you really want to pass an array, rather than a container or a pointer to the first element of anarray, you can declare a parameter of type reference to array.
For example:Section 12.2.2Array Arguments319void f(int(&r)[4]);void g(){int a1[] = {1,2,3,4};int a2[] = {1,2};f(a1);f(a2);// OK// error : wrong number of elements}Note that the number of elements is part of a reference-to-array type. That makes such referencesfar less flexible than pointers and containers (such as vector). The main use of references to arraysis in templates, where the number of elements is then deduced. For example:template<class T, int N> void f(T(&r)[N]){// ...}int a1[10];double a2[100];void g(){f(a1);f(a2);}// T is int; N is 10// T is double; N is 100This typically gives rise to as many function definitions as there are calls to f() with distinct arraytypes.Multidimensional arrays are tricky (see §7.3), but often arrays of pointers can be used instead,and they need no special treatment.
For example:const char∗ day[] = {"mon", "tue", "wed", "thu", "fri", "sat", "sun"};As ever, vector and similar types are alternatives to the built-in, low-level arrays and pointers.12.2.3 List ArgumentsA {}-delimited list can be used as an argument to a parameter of:[1] Type std::initializer_list<T>, where the values of the list can be implicitly converted to T[2] A type that can be initialized with the values provided in the list[3] A reference to an array of T, where the values of the list can be implicitly converted to TTechnically, case [2] covers all examples, but I find it easier to think of the three cases separately.Consider:320FunctionsChapter 12template<class T>void f1(initializer_list<T>);struct S {int a;string s;};void f2(S);template<class T, int N>void f3(T (&r)[N]);void f4(int);void g(){f1({1,2,3,4});f2({1,"MKS"});f3({1,2,3,4});f4({1});}// T is int and the initializer_list has size() 4// f2(S{1,"MKS"})// T is int and N is 4// f4(int{1});If there is a possible ambiguity, an initializer_list parameter takes priority.
For example:template<class T>void f(initializer_list<T>);struct S {int a;string s;};void f(S);template<class T, int N>void f(T (&r)[N]);void f(int);void g(){f({1,2,3,4});f({1,"MKS"});f({1});}// T is int and the initializer_list has size() 4// calls f(S)// T is int and the initializer_list has size() 1The reason that a function with an initializer_list argument take priority is that it could be very confusing if different functions were chosen based on the number of elements of a list. It is not possible to eliminate every form of confusion in overload resolution (for example, see §4.4, §17.3.4.1),but giving initializer_list parameters priority for {}-list arguments seems to minimize confusion.Section 12.2.3List Arguments321If there is a function with an initializer-list argument in scope, but the argument list isn’t amatch for that, another function can be chosen.
The call f({1,"MKS"}) was an example of that.Note that these rules apply to std::initializer_list<T> arguments only. There are no special rulesfor std::initializer_list<T>& or for other types that just happen to be called initializer_list (in someother scope).12.2.4 Unspecified Number of ArgumentsFor some functions, it is not possible to specify the number and type of all arguments expected in acall. To implement such interfaces, we have three choices:[1] Use a variadic template (§28.6): this allows us to handle an arbitrary number of arbitrarytypes in a type-safe manner by writing a small template metaprogram that interprets theargument list to determine its meaning and take appropriate actions.[2] Use an initializer_list as the argument type (§12.2.3).
This allows us to handle an arbitrarynumber of arguments of a single type in a type-safe manner. In many contexts, suchhomogeneous lists are the most common and important case.[3] Terminate the argument list with the ellipsis (...), which means ‘‘and maybe some morearguments.’’ This allows us to handle an arbitrary number of (almost) arbitrary types byusing some macros from <cstdarg>. This solution is not inherently type-safe and can behard to use with sophisticated user-defined types. However, this mechanism has beenused from the earliest days of C.The first two mechanisms are described elsewhere, so I describe only the third mechanism (eventhough I consider it inferior to the others for most uses).
For example:int printf(const char∗ ...);This specifies that a call of the standard-library function printf() (§43.3) must have at least one argument, a C-style string, but may or may not have others. For example:printf("Hello, world!\n");printf("My name is %s %s\n", first_name, second_name);printf("%d + %d = %d\n",2,3,5);Such a function must rely on information not available to the compiler when interpreting its argument list. In the case of printf(), the first argument is a format string containing special charactersequences that allow printf() to handle other arguments correctly; %s means ‘‘expect a char∗ argument’’ and %d means ‘‘expect an int argument.’’ However, the compiler cannot in general ensurethat the expected arguments are really provided in a call or that an argument is of the expected type.For example:#include <cstdio>int main(){std::printf("My name is %s %s\n",2);}This is not valid code, but most compilers will not catch this error.
At best, it will produce somestrange-looking output (try it!).322FunctionsChapter 12Clearly, if an argument has not been declared, the compiler does not have the informationneeded to perform the standard type checking and type conversion for it. In that case, a char or ashort is passed as an int and a float is passed as a double.
This is not necessarily what the programmer expects.A well-designed program needs at most a few functions for which the argument types are notcompletely specified. Overloaded functions, functions using default arguments, functions takinginitializer_list arguments, and variadic templates can be used to take care of type checking in mostcases when one would otherwise consider leaving argument types unspecified. Only when both thenumber of arguments and the types of arguments vary and a variadic template solution is deemedundesirable is the ellipsis necessary.The most common use of the ellipsis is to specify an interface to C library functions that weredefined before C++ provided alternatives:int fprintf(FILE∗, const char∗ ...);int execl(const char∗ ...);// from <cstdio>// from UNIX headerA standard set of macros for accessing the unspecified arguments in such functions can be found in<cstdarg>.