template
typename... Ts> // types of arguments to use
void doSomeWork(Ts&&... params) {
create local T object from params ...
…
}
There are two ways to turn the line of pseudocode into real code (see Item 25for information about std::forward):
T localObject (std::forward(params)... ); // using parens
T localObject {std::forward(params)... }; // using braces
So consider this calling code:
std::vector v;
…
doSomeWork>(10, 20);
If doSomeWorkuses parentheses when creating localObject, the result is a std::vectorwith 10 elements. If doSomeWorkuses braces, the result is a std::vectorwith 2 elements. Which is correct? The author of doSomeWorkcan't know. Only the caller can.
This is precisely the problem faced by the Standard Library functions std::make_uniqueand std::make_shared(see Item 21). These functions resolve the problem by internally using parentheses and by documenting this decision as part of their interfaces. [1] More flexible designs — ones that permit callers to determine whether parentheses or braces should be used in functions generated from a template — are possible. For details, see the 5 June 2013 entry of Andrzej's C++ blog , “ Intuitive interface — Part I . ”
Things to Remember
• Braced initialization is the most widely usable initialization syntax, it prevents narrowing conversions, and it's immune to C++'s most vexing parse.
• During constructor overload resolution, braced initializers are matched to std::initializer_listparameters if at all possible, even if other constructors offer seemingly better matches.
• An example of where the choice between parentheses and braces can make a significant difference is creating a std::vector< numeric type >with two arguments.
• Choosing between parentheses and braces for object creation inside templates can be challenging.
Item 8: Prefer nullptrto 0and NULL.
So here's the deal: the literal 0is an int, not a pointer. If C++ finds itself looking at 0in a context where only a pointer can be used, it'll grudgingly interpret 0as a null pointer, but that's a fallback position. C++'s primary policy is that 0is an int, not a pointer.
Practically speaking, the same is true of NULL. There is some uncertainty in the details in NULL's case, because implementations are allowed to give NULLan integral type other than int(e.g., long). That's not common, but it doesn't really matter, because the issue here isn't the exact type of NULL, it's that neither 0nor NULLhas a pointer type.
In C++98, the primary implication of this was that overloading on pointer and integral types could lead to surprises. Passing 0or NULLto such overloads never called a pointer overload:
void f(int); // three overloads of f
void f(bool);
void f(void*);
f(0); // calls f(int), not f(void*)
f(NULL); // might not compile, but typically calls
// f(int). Never calls f(void*)
The uncertainty regarding the behavior of f(NULL)is a reflection of the leeway granted to implementations regarding the type of NULL. If NULLis defined to be, say, 0L(i.e., 0as a long), the call is ambiguous, because conversion from longto int, longto bool,and 0Lto void*are considered equally good. The interesting thing about that call is the contradiction between the apparent meaning of the source code (“I'm calling fwith NULL— the null pointer”) and its actual meaning (“I'm calling fwith some kind of integer — not the null pointer”). This counterintuitive behavior is what led to the guideline for C++98 programmers to avoid overloading on pointer and integral types. That guideline remains valid in C++11, because, the advice of this Item notwithstanding, it's likely that some developers will continue to use 0and NULL, even though nullptris a better choice.
nullptr's advantage is that it doesn't have an integral type. To be honest, it doesn't have a pointer type, either, but you can think of it as a pointer of all types. nullptr's actual type is std::nullptr_t, and, in a wonderfully circular definition, std::nullptr_tis defined to be the type of nullptr. The type std::nullptr_timplicitly converts to all raw pointer types, and that's what makes nullptract as if it were a pointer of all types.
Calling the overloaded function fwith nullptrcalls the void*overload (i.e., the pointer overload), because nullptrcan't be viewed as anything integral:
f(nullptr); // calls f(void*) overload
Using nullptrinstead of 0or NULLthus avoids overload resolution surprises, but that's not its only advantage. It can also improve code clarity, especially when autovariables are involved. For example, suppose you encounter this in a code base:
auto result = findRecord( /* arguments */ );
if ( result == 0) {
…
}
If you don't happen to know (or can't easily find out) what findRecordreturns, it may not be clear whether resultis a pointer type or an integral type. After all, 0(what resultis tested against) could go either way. If you see the following, on the other hand,
auto result = findRecord( /* arguments */ );
if ( result == nullptr) {
…
}
there's no ambiguity: resultmust be a pointer type.
nullptrshines especially brightly when templates enter the picture. Suppose you have some functions that should be called only when the appropriate mutex has been locked. Each function takes a different kind of pointer:
int f1(std::shared_ptr spw); // call these only when
double f2(std::unique_ptr upw); // the appropriate
bool f3(Widget* pw); // mutex is locked
Calling code that wants to pass null pointers could look like this:
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxGuard = // C++11 typedef; see Item 9
std::lock_guard;
…
{
MuxGuard g(f1m); // lock mutex for f1
auto result = f1( 0); // pass 0 as null ptr to f1
} // unlock mutex
…
{
MuxGuard g(f2m); // lock mutex for f2
auto result = f2( NULL); // pass NULL as null ptr to f2
} // unlock mutex
…
{
Читать дальше