16.4.3. Перенаправление пакетов параметров
Версия функции emplace_back() для класса StrVec также должна быть с переменным количеством аргументов, поскольку у класса string много конструкторов, которые отличаются своими параметрами.
Поскольку желательно быть в состоянии использовать конструктор перемещения класса string, необходимо будет также сохранять всю информацию о типах аргументов, переданных функции emplace_back().
Как уже упоминалось, сохранение информации типа — двухступенчатый процесс. Во-первых, для сохранения информации типа аргументов параметры функции emplace_back() следует определить как ссылки на r-значение параметра типа шаблона (см. раздел 16.2.7):
class StrVec {
public:
template <class... Args> void emplace_back(Args&&...);
// остальные члены, как в разделе 13.5
};
Схема && в развертывании пакета параметров шаблона означает, что каждый параметр функции будет ссылкой на r-значение на соответствующий ей аргумент.
Во-вторых, функцию forward() следует использовать для сохранения первоначальных типов аргументов, когда функция emplace_back() передает их функции construct() (см. раздел 16.2.7):
template <class... Args>
inline
void StrVec::emplace_back(Args&&... args) {
chk_n_alloc(); // пересоздает StrVec при необходимости
alloc.construct(first_free++, std::forward<Args>(args)...);
}
Тело функции emplace_back() вызывает функцию chk_n_alloc() (см. раздел 13.5), чтобы гарантировать наличие достаточного места для элемента, и вызывает функцию construct(), чтобы создать элемент в позиции, на которую указывает указатель first_free.
std::forward<Args>(args)...
Развертывание в вызове функции construct() разворачивает оба пакета: параметров шаблона Args и параметров функции args. Эта схема создает элементы в формате:
std::forward<Ti>(ti)
где Ti представляет тип i-го элемента в пакете параметров шаблона, a ti представляет i-й элемент в пакете параметров функции. Например, если svec имеет тип StrVec, то при вызове
svec.emplace_back(10, 'c'); // добавит cccccccccc как новый последний
// элемент
схема в вызове функции construct() развернется в
std::forward<int>(10), std::forward<char>(c)
Использование функции forward() в этом вызове гарантирует, что если функция emplace_back() будет вызвана с r-значением, то функция construct() также получит r-значение. Например, в вызове
svec.emplace_back(s1 + s2); // использует конструктор перемещения
аргумент функции emplace_back() является r-значением, которое передается функции construct() как
std::forward<string>(string("the end"))
Типом результата вызова forward<string> будет strings&, поэтому функция construct() будет вызвана со ссылкой на r-значение. Функция construct(), в свою очередь, перенаправит этот аргумент конструктору перемещения класса string, чтобы создать этот элемент.
Совет. Перенаправление и шаблоны с переменным количеством аргументов
Функции с переменным количеством аргументов зачастую перенаправляют свои параметры другим функциям. Форма таких функций, как правило, подобна функции emplace_back():
// у функции fun() может быть любое количество параметров, каждый
// из которых является ссылкой r-значения на тип параметра шаблона
template<typename... Args>
void fun(Args&&... args) // развертывание Args в список ссылок
// на r-значения
{
// аргумент work() развертывает как Args, так и args
work(std::forward<Args>(args)...);
}
Здесь предполагается перенаправить все аргументы функции fun() другой функции, work(), которая, по-видимому, осуществляет реальную работу. Как и вызов функции construct() в функции emplace_back(), развертывание в вызове функции work() разворачивает и пакет параметров шаблона, и пакет параметров функции.
Поскольку параметры функции fun() являются ссылками на r-значение, функции fun() можно передать аргументы любого типа; поскольку для передачи этих аргументов используется функция std::forward(), вся информация о типах этих аргументов будет сохраняться в вызове функции work().
Упражнения раздела 16.4.3
Упражнение 16.58. Напишите функцию emplace_back() для собственного класса StrVec и для класса Vec, написанного в упражнении раздела 16.1.2.
Упражнение 16.59. С учетом того, что s имеет тип string, объясните вызов svec.emplace_back(s).
Упражнение 16.60. Объясните, как работает функция make_shared() (см. раздел 12.1.1).
Упражнение 16.61. Определите собственную версию функции make_shared().