Читать «Эффективный и современный С++. 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.