Категории
Самые читаемые
Лучшие книги » Компьютеры и Интернет » Программирование » Язык программирования C++. Пятое издание - Стенли Липпман

Язык программирования C++. Пятое издание - Стенли Липпман

Читать онлайн Язык программирования C++. Пятое издание - Стенли Липпман

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 229 230 231 232 233 234 235 236 237 ... 297
Перейти на страницу:

Теперь рассмотрим второе присвоение, которое вызывает функцию std::move(s1). В этом вызове аргументом функции move() является l-значение. Поэтому на сей раз:

• выведенным типом Т будет string& (ссылка на тип string, а не просто string);

• следовательно, экземпляр шаблона remove_reference создается с типом string&;

• тип-член type класса remove_reference<string&> будет иметь тип string;

• типом возвращаемого значения функции move() все еще будет string&&;

• параметр t функции move() будет создан как экземпляр string& &&, который сворачивается в string&.

Таким образом, этот вызов создает экземпляр шаблона move<string&>, который является точно тем, что необходимо для связи ссылки на r-значение с l-значением.

string&& move(string &t)

Тело этого экземпляра возвращает тип static_cast<string&&>(t). В данном случае типом t является string&, который приведение преобразует в тип string&&.

Оператор static_cast поддерживает приведение l-значения к ссылке на r-значение

Обычно оператор static_cast может выполнить только доступные преобразования (см. раздел 16.3). Однако для ссылок на r-значение есть специальное разрешение: даже при том, что нельзя неявно преобразовать l-значение в ссылку на r-значение, используя оператор static_cast, можно явно привести l-значение к ссылке на r-значение.

Привязка ссылки на r-значение к l-значению создает код, который работает с разрешением ссылке на r-значение заменять l-значение. Иногда, как в случае с функцией reallocate() класса StrVec (см. раздел 13.6.1), известно, что замена l-значения безопасна. Разрешая осуществлять это приведение, язык позволяет его использование. Вынуждая использовать приведение, язык пытается предотвратить его случайное использование.

И наконец, хотя такие приведения можно написать непосредственно, намного проще использовать библиотечную функцию move(). Кроме того, использование функции std::move() существенно облегчает поиск в коде места, потенциально способного заменить l-значения.

Упражнения раздела 16.2.6

Упражнение 16.46. Объясните, что делает этот цикл из функции StrVec::reallocate() (раздел 13.5):

for (size_t i = 0; i != size(); ++i)

 alloc.construct(dest++, std::move(*elem++));

16.2.7. Перенаправление

Некоторые функции должны перенаправлять другим функциям один или несколько своих аргументов с неизменными типами. В таких случаях необходимо сохранить всю информацию о перенаправленных аргументах, включая то, является ли тип аргумента константой и является ли аргумент l- или r-значением.

В качестве примера напишем функцию, получающую вызываемое выражение и два дополнительных аргумента. Функция вызовет предоставленное вызываемое выражение с другими двумя аргументами в обратном порядке. Вот первый фрагмент функции обращения:

// шаблон, получающий вызываемое выражение и два параметра

// вызывает предоставленное выражение с "обращенными" параметрами

// flip1 - неполная реализация: спецификатор const верхнего уровня и

// ссылки теряются

template <typename F, typename T1, typename T2>

void flip1(F f, T1 t1, T2 t2) {

 f(t2, t1);

}

Этот шаблон работает прекрасно, пока он не используется для вызова функции со ссылочным параметром:

void f(int v1, int &v2) // обратите внимание, v2 - ссылка

{

 cout << v1 << " " << ++v2 << endl;

}

Здесь функция f() изменяет значение аргумента, привязанного к параметру v2. Но если происходит вызов функции f() через шаблон flip1, внесенные функцией f() изменения не затронут первоначальный аргумент:

f(42, i);        // f() изменяет свой аргумент i

flip1(f, j, 42); // вызов f() через flip1 оставляет j неизменным

Проблема в том, что j передается параметру t1 шаблона flip1. Этот параметр имеет простой, не ссылочный тип int, а не int&. Таким образом, этот вызов создает следующий экземпляр шаблона flip1:

void flip1(void(*fcn)(int, int&), int t1, int t2);

Значение j копируется в t1. Ссылочный параметр в функции f() связан с t1, а не с j. 

Определение параметров функции, хранящих информацию типа

Чтобы передать ссылку через функцию, необходимо переписать ее так, чтобы параметры сохраняли принадлежность своих аргументов к l-значениям. Немного поразмыслив, можно предположить, что константность аргументов также необходимо сохранить.

Всю информацию о типе аргумента можно сохранить, определив соответствующий ему параметр функции как ссылку на r-значение параметра типа шаблона. Использование ссылочного параметра (l- или r-значение) позволяет сохранить константность, поскольку спецификатор const в ссылочном типе нижнего уровня. Благодаря сворачиванию ссылок (см. раздел 16.2.5), если определить параметры функции как T1&& и T2&&, можно сохранить принадлежность к l- или r-значениям аргументов функции (см. раздел 16.2.5):

template <typename F, typename T1, typename T2>

void flip2(F f, T1 &&t1, T2 &&t2) {

 f(t2, t1);

}

Как и прежде, если происходит вызов flip2(f, j, 42), l-значение j передается параметру t1. Однако в функции flip() для T1 выводится тип int&, а значит, тип t1 сворачивается в int&. Ссылка t1 связана с j. Когда функция flip() вызывает функцию f(), ссылочный параметр v2 в функции f() привязан к t1, который, в свою очередь, привязан к j. Когда функция f() осуществляет инкремент v2, это изменяет значение j.

Параметр функции, являющийся ссылкой на r-значение параметра типа шаблона (т.е. Т&&), сохраняет константность и принадлежность к l- или r-значениям соответствующих ему аргументов.

Эта версия функции flip() решает одну половину проблемы. Она работает прекрасно с функциями, получающими ссылки на l-значение, но неприменима для вызова функций с параметрами ссылок на r-значение. Например:

void g(int &&i, int& j) {

 cout << i << " " << j << endl;

}

Если попытаться вызывать функцию g() через функцию flip(), то для параметра ссылки на r-значение функции g() будет передан параметр t2. Даже если функции flip() было передано r-значение, функции g() будет передан параметр, носящий в функции flip() имя t2:

flip2(g, i, 42); // ошибка: нельзя инициализировать int&& из l-значения

Параметр функции, как и любая другая переменная, является выражением l-значения (см. раздел 13.6.1). В результате вызов функции g() в функции flip() передает l-значение параметру ссылки на r-значение функции g().

Использование функции std::forward() для сохранения информации типа в вызове

Чтобы передать функции flip() параметры способом, сохраняющим типы первоначальных аргументов, можно использовать новую библиотечную функцию forward(). Как и функция move(), функция forward() определяется в заголовке utility. В отличие от функции move(), функцию forward() следует вызывать с явным аргументом шаблона (см. раздел 16.2.2). Для этого явного аргумента типа функция forward() возвращает ссылку на r-значение. Таким образом, типом возвращаемого значения функции forward<T> будет Т&&.

Обычно функцию forward() используют для передачи параметра функции, который определен как ссылка на r-значение, параметру типа шаблона. Благодаря сворачиванию ссылок для типа возвращаемого значения функция forward() сохраняет характер (l- или r-значение) переданного ей аргумента:

template <typename Type> intermediary(Type &&arg) {

 finalFcn(std::forward<Type>(arg)); // ...

}

Здесь Type используется как тип явного аргумента шаблона функции forward() (выводимый из arg). Поскольку arg — это ссылка на r-значение для параметра типа шаблона, параметр Type представит всю информацию типа в аргументе, переданном параметру arg. Если этот аргумент будет r-значением, то параметр Type будет иметь обычный (не ссылочный) тип и функция forward<Type>() возвратит Type&&. Если аргумент будет l-значением, то (благодаря сворачиванию ссылок) типом параметра Type будет ссылка на l-значение. В данном случае типом возвращаемого значения будет ссылка на r-значение для типа ссылки на l-значение. Снова благодаря сворачиванию ссылок (на сей раз для типа возвращаемого значения) функция forward<Type>() возвратит тип ссылки на l-значение.

1 ... 229 230 231 232 233 234 235 236 237 ... 297
Перейти на страницу:
На этой странице вы можете бесплатно скачать Язык программирования C++. Пятое издание - Стенли Липпман торрент бесплатно.
Комментарии