MuxGuard g(f3m); // lock mutex for f3
auto result = f3( nullptr); // pass nullptr as null ptr to f3
} // unlock mutex
The failure to use nullptrin the first two calls in this code is sad, but the code works, and that counts for something. However, the repeated pattern in the calling code — lock mutex, call function, unlock mutex — is more than sad. It's disturbing. This kind of source code duplication is one of the things that templates are designed to avoid, so let's templatize the pattern:
template
typename MuxType,
typename PtrType>
auto lockAndCall(FuncType func,
MuxType& mutex,
PtrType ptr) -> decltype(func(ptr)) {
MuxGuard g(mutex);
return func(ptr);
}
If the return type of this function ( auto … -> decltype(func(ptr)) has you scratching your head, do your head a favor and navigate to Item 3, which explains what's going on. There you'll see that in C++14, the return type could be reduced to a simple decltype(auto):
template
typename MuxType,
typename PtrType>
decltype(auto)lockAndCall(FuncType func, // C++14
MuxType& mutex,
PtrType ptr) {
MuxGuard g(mutex);
return func(ptr);
}
Given the lockAndCalltemplate (either version), callers can write code like this:
auto result1 = lockAndCall(f1, f1m, 0); // error!
…
auto result2 = lockAndCall(f2, f2m, NULL); // error!
…
auto result3 = lockAndCall(f3, f3m, nullptr); // fine
Well, they can write it, but, as the comments indicate, in two of the three cases, the code won't compile. The problem in the first call is that when 0is passed to lockAndCall, template type deduction kicks in to figure out its type. The type of 0is, was, and always will be int, so that's the type of the parameter ptrinside the instantiation of this call to lockAndCall. Unfortunately, this means that in the call to funcinside lockAndCall, an intis being passed, and that's not compatible with the std::shared_ptrparameter that f1expects. The 0passed in the call to lockAndCallwas intended to represent a null pointer, but what actually got passed was a run-of-the-mill int. Trying to pass this intto f1as a std::shared_ptris a type error. The call to lockAndCallwith 0fails because inside the template, an intis being passed to a function that requires a std::shared_ptr.
The analysis for the call involving NULLis essentially the same. When NULLis passed to lockAndCall, an integral type is deduced for the parameter ptr, and a type error occurs when ptr — an intor int-like type — is passed to f2, which expects to get a std::unique_ptr.
In contrast, the call involving nullptrhas no trouble. When nullptris passed to lockAndCall, the type for ptris deduced to be std::nullptr_t. When ptris passed to f3, there's an implicit conversion from std::nullptr_tto Widget*, because std::nullptr_timplicitly converts to all pointer types.
The fact that template type deduction deduces the “wrong” types for 0and NULL(i.e., their true types, rather than their fallback meaning as a representation for a null pointer) is the most compelling reason to use nullptrinstead of 0or NULLwhen you want to refer to a null pointer. With nullptr, templates pose no special challenge. Combined with the fact that nullptrdoesn't suffer from the overload resolution surprises that 0and NULLare susceptible to, the case is ironclad. When you want to refer to a null pointer, use nullptr, not 0or NULL.
Things to Remember
• Prefer nullptrto 0and NULL.
• Avoid overloading on integral and pointer types.
Item 9: Prefer alias declarations to typedefs.
I'm confident we can agree that using STL containers is a good idea, and I hope that Item 18convinces you that using std::unique_ptris a good idea, but my guess is that neither of us is fond of writing types like “ std::unique_ptr>” more than once. Just thinking about it probably increases the risk of carpal tunnel syndrome.
Avoiding such medical tragedies is easy. Introduce a typedef:
typedef
std::unique_ptr>
UPtrMapSS;
But typedefs are soooo C++98. They work in C++11, sure, but C++11 also offers alias declarations :
using UPtrMapSS =
std::unique_ptr>;
Given that the typedefand the alias declaration do exactly the same thing, it's reasonable to wonder whether there is a solid technical reason for preferring one over the other.
There is, but before I get to it, I want to mention that many people find the alias declaration easier to swallow when dealing with types involving function pointers:
// FP is a synonym for a pointer to a function taking an int and
// a const std::string& and returning nothing
typedefvoid (* FP)(int, const std::string&); // typedef
// same meaning as above
using FP= void (*)(int, const std::string&); // alias
// declaration
Of course, neither form is particularly easy to choke down, and few people spend much time dealing with synonyms for function pointer types, anyway, so this is hardly a compelling reason to choose alias declarations over typedefs.
But a compelling reason does exist: templates. In particular, alias declarations may be templatized (in which case they're called alias templates ), while typedefs cannot. This gives C++11 programmers a straightforward mechanism for expressing things that in C++98 had to be hacked together with typedefs nested inside templatized structs. For example, consider defining a synonym for a linked list that uses a custom allocator, MyAlloc. With an alias template, it's a piece of cake:
template // MyAllocList
using MyAllocList= std::list>; // is synonym for
// std::list
// MyAlloc>
MyAllocList lw; // client code
With a typedef, you pretty much have to create the cake from scratch:
template // MyAllocList::type
struct MyAllocList { // is synonym for
typedefstd::list> type; // std::list
}; // MyAlloc>
MyAllocList::type lw; // client code
Читать дальше