Читать «Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14» онлайн - страница 47
Скотт Мейерс
Вызывающий код с передачей нулевых указателей может выглядеть следующим образом:
std::mutex f1m, f2m, f3m; // Мьютексы для f1, f2 и f3
using MuxGuard = // C++11 typedef; см. раздел 3.3
std::lock_guard<std::mutex>;
{
MuxGuard g(f1m); // Блокировка мьютекса для f1
auto result = f1(0); // Передача 0 функции f1
} // Разблокирование мьютекса
…
{
MuxGuard g(f2m); // Блокировка мьютекса для f2
auto result = f2(NULL); // Передача NULL функции f2
} // Разблокирование мьютекса
…
{
MuxGuard g(f3m); // Блокировка мьютекса для f3
auto result = f3(nullptr); // Передача nullptr функции f3
} // Разблокирование мьютекса
То, что в первых двух вызовах не был передан nullptr
, грустно; тем не менее код работает, а это чего-то да стоит. Однако повторяющиеся действия еще более грустны. Они просто беспокоят. Во избежание дублирования такого вида и предназначаются шаблоны, так что давайте превратим эти действия в шаблон.
template<typename FuncType,
typename MuxType,
typename PtrType>
auto lockAndCall(FuncType func,
MuxType& mutex,
PtrType ptr) -> decltype(func(ptr)) {
using MuxGuard = std::lock_guard<MuxType>;
MuxGuard g(mutex);
return func(ptr);
}
Если возвращаемый тип этой функции (auto...->decltype(func(ptr)
) заставляет вас чесать затылок, обратитесь к разделу 1.3, в котором объясняется происходящее. Там вы узнаете, что в С++ 14 возвращаемый тип можно свести к простому decltype(auto)
:
template<typename FuncType,
typename MuxType,
typename PtrType>
decltype(auto) lockAndCall(FuncType func, // С++14
MuxType& mutex,
PtrType ptr) {
using MuxGuard = std::lock_guard<MuxType>;
MuxGuard g(mutex);
return func(ptr);
}
Для данного шаблона lockAndCall
(любой из версий), вызывающий код может иметь следующий вид:
auto result1 = lockAndCall(f1, f1m, 0); // Ошибка!
auto result2 = lockAndCall(f2, f2m, NULL); // Ошибка!
auto result3 = lockAndCall(f3, f3m, nullptr); // OK
Такой код можно написать, но, как показывают комментарии, в двух случаях из трех этот код компилироваться не будет. В первом вызове проблема в том, что когда 0
передается в lockAndCall
, происходит вывод соответствующего типа шаблона. Типом 0
является, был и всегда будет int
, как и тип параметра ptr
в инстанцировании данного вызова lockAndCall
. К сожалению, это означает, что в вызов func
в lockAndCall
передается int
, а этот тип несовместим с параметром std::shared_ptr<Widget>
, ожидаемым функцией f1
. Значение 0
, переданное в вызове lockAndCall
, призвано представлять нулевой указатель, но на самом деле передается заурядный int
. Попытка передать этот int функции f1
как std::shared_ptr<Widget>
представляет собой ошибку типа. Вызов lockAndCall
с 0
оказывается неудачным, поскольку в шаблоне функции, которая требует аргумент типа std::shared_ptr<Widget>
, передается значение int
.