Добавил:
t.me Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп

.pdf
Скачиваний:
9
Добавлен:
16.07.2023
Размер:
31.34 Mб
Скачать

6.3.

Параметризованные

операции

123

template<typename Т>

class Vector2

 

{

 

 

puЫic:

 

 

using

value

type

11

 

 

Т;

11 Конструктор из Vector2(initializ

списка инициализации: er_list<T>);

11 Конструктор

из

диапазона

template<typename

Iter>

Vector2(Iter Ь,

Iter е);

11 ...

 

 

} ;

[Ь:е):

Vector2

Vector2

vl

(1,2,3,4,5};

// Тип элементов

v2(vl.begin(),vl.begin()+2);

-

int

Очевидно,

что

v2

должен

быть

Vector2<int>,

но

без

помощи

компиля­

тор

не

может

это

вывести.

В

коде

указано

только,

что

существует

конструктор

из

пары

значений

одного

и того же

типа.

Без

языковой

поддержки

концептов

(§7.2)

компилятор

ничего

не

может

предполагать

об

этом

типе.

Чтобы

разре­

шить

вывод,

мы

можем

добавить

правило

вывода

после

объявления

Vector2:

template<typename

Iter>

Vector2(Iter,Iter)

-> Vector2<typename

Iter::value_type>;

То

есть

если

компилятор

встречает

Vector2,

инициализированный

парой

итераторов,

то

он

должен

вывести

Vector2::

value

_

type

как

тип

значения

итератора. Применение

правил

вывода

может

сопровождаться

очень

тонкими

эффек­

тами,

поэтому

лучше

всего

разрабатывать

шаблоны

классов

таким

образом,

чтобы

правила

вывода

были

не

нужны.

Однако

в

настоящий

момент

стан­

дартная

библиотека

полна

классов,

которые

(пока)

не

используют

концепты

(§7.2)

и

содержат

неоднозначности,

поэтому

в

ней

используется

довольно

много

правил

вывода.

6.3.

Параметризованные

операции

Шаблоны

имеют

гораздо

больше

возможностей,

чем

простая

параметриза­

ция

контейнера

типом

элемента.

В

частности,

они

широко

используются

для

параметризации

типов

и

алгоритмов

стандартной

библиотеки(§

11.6, §12.6).

Существует

три

средства

выражения

операции,

параметризованной

типа­

ми

или

значениями:

6.3.

Параметризованные

операции

125

Шаблон

функции

может

быть

функцией-членом,

но не

виртуальной

функ­

цией

-

компилятор

не

в

состоянии

знать

все

инстанцирования

такого

шабло­

на

в

программе,

а

потому

не

в

состоянии

корректно

генерировать

vtЫ

в

дан­

ной

ситуации

(§4.4).

6.3.2.

Функциональные

объекты

Одной

особенно

полезной

разновидностью

шаблона

является

функцио­

нальный

объект

(иногда

называемый

функтором),

используемый

для

опреде­

ления

объектов,

которые

могут

быть

вызваны

с

использованием

синтаксиса

вызова

функций.

Например:

template<typename class Less than

Т>

const

Т

val;

//

Значение,

с

которым

выполняется

сравнение

puЫic:

 

 

 

Less_than(const

Т&

v)

bool

operator()

(const

{

 

 

 

 

return x<val;

 

};

 

 

 

:val{v}

{ }

Т& х)

const

//

Оператор

вызова

Функция

operator

()

реализует

оператор

"вызова

функции"

или

просто

"вызова" -

(). Можно определить

именованные

than для некоторых аргументов типа:

 

переменные

типа

Less

Less_than

lti{42};

 

Less_than

lts{"Backus"s};

Less_than<string>

lts2{"Naur"};

// / / // // //

lti

(i)

сравнивает

i и

42

(i<42)

1 ts

(s)

сравнивает

s<"Backus"

"Naur"

-

строка в

стиле

С, поэтому

нам

нужен

<string>

для

<

 

правильного оператора

 

Такой

объект

можно

вызвать

так

же,

как

функцию:

void

fct(int

{

 

 

 

bool

Ы

 

bool

Ь2

 

11 ...

n,

const

string & s)

 

lti(n);

//

true,

если

п<42

lts(s);

//

true,

если

s<"Backus"

Такие

функциональные

объекты

широко

используются

в

качестве

аргумен­

тов алгоритмов.

Например,

можно

подсчитать

количество

вхождений

встре­

чающихся

значений,

для

которых

предикат

возвращает

значение

true:

template<typename

С,

typename

Р>

//

requires

Sequence<C>

&&

CallaЫe<P,Value_type<P>>

int

count(const

С&

с,

Р

pred)

 

 

 

 

 

6.3.

Параметризованные операции

cout

<<

"Количество значений,

меньших " <<

s

 

 

 

<<

": "

<<

count (lst, [&]

(const

string&

а) {

return a<s;

})

 

<<

'\n';

 

 

 

 

 

 

 

127

Запись

[

& ]

(

int

а)

{

return

а<х;

}

называется

лямбда-выражением.

Оно

генерирует

функциональный

объект

точно

так

же,

как

и

Less

than<int>

{х }.

Конструкция

[

& ]

представляет

собой

список

захвата,

указывающий,

что

локальные

имена,

используемые

в

теле

лямбда-выражения

(такие,

как

х),

будут

досrупны

по

ссылке.

Для

того

чтобы

"захватить"

только

х,

мы

можем

сделать

следующее:

[

&х]

.

Если

мы

хотим

передать

генерируемому

объекrу

копию

х,

то

для

этого

должны

использовать

запись

[

=х]

.

Запись

[]

указыва­

ет,

что

не

захватываются

никакие

локальные

имена;

запись

для

захвата всех

локальных

имен

по

ссылке

-

[

& ] ,

а

запись

для

захвата

всех

локальных

имен

по

значению

-

[

=]

.

Использование

лямбда-выражений

может

быть

удобным

и

кратким,

но

од­

новременно

и

неясным.

Для

нетривиальных

действий

(скажем,

более

чем

для

простого

выражения)

я предпочитаю

именовать

операцию,

чтобы

более

четко

изложить

ее

цель

и

сделать

ее

досrупной

для

использования

в

нескольких

ме­

стах

программы.

В

§4.5.3

мы

отметили

раздражение

из-за

необходимости

писать

множе­

ство

функций

для

выполнения

операций

над

элементами

векторов

указателей

и

unique_ptr,

таких

как

draw_all

()

и

rotate_all

().Функциональные

объекты

частности,

лямбда-выражения)

могут

помочь

нам,

позволяя

отде­

лить

обход

контейнера

от

указания

того,

что

должно

быть

выполнено

с

каж­

дым элементом.

 

Во-первых, нам

нужна функция, применяющая операцию

екrу, на который указывают элементы контейнера указателей:

к

каждому

объ­

template<typename

С,

typename

Oper>

 

 

 

void for_all(C&

с,

Oper

ор)

//

Считаем,

что С -

контейнер указателей

11 requires Sequence<C>

&&

CallaЫe<Oper,Value_type<C>>

(см. §7.2.1)

{

 

 

 

 

 

 

 

 

 

 

for

(auto& х

:

с)

 

 

 

 

 

 

 

ор(х);

//Передача

вор()

ссылки

на

каждый

элемент

Теперь

мы

можем написать версию

user ()

ства функций

_all:

 

void

user2

()

 

 

{

 

 

 

 

 

vector<unique_ptr<Shape>> v;

 

 

while

(cin)

 

 

v.push_back[read_shape(cin));

 

из

§4.5

без

создания

множе­

128

Глава

6.

Шаблоны

 

11

draw_all ():

ps) { ps->draw(); });

for_all(v,

[]

(unique_ptr<Shape>&

11

rotate_all (45):

 

for_all(v,

[]

(unique_ptr<Shape>&

ps) { ps->rotate(45);

});

Я

передаю

в

лямбда-выражение

unique_ptr<Shape>&,

так что

функ­

ция

for

all

()

не

должна

заботиться

о

том,

как

именно

хранятся объекты.

В

частности,

вызовы

for

_

all

()

не

влияют

на

время

жизни

переданных

Shape,

а

тела

лямбда-выражений

используют

аргумент

так

же,

как

если

бы

он

был простым указателем.

Как и функция, лямбда-выражение

может

быть

обобщенным.

Например:

template<class S>

 

 

 

 

void

rotate_and_draw(vector<S>& v,

int r)

 

{

 

 

 

 

 

 

for_all(v,[](auto&

s){

s->rotate(r);

s->draw();

});

Здесь,

как

и

в

объявлениях

переменных,

auto

означает,

что

в

качестве

ини­

циализатора

принимается

любой

тип

(аргумент

рассматривается

как

инициа­

лизирующий

формальный

параметр

в

вызове).

Это

делает

лямбда-выражение

с

параметром

auto

шаблоном,

обобщенным

лямбда-выражением.

По причи­

нам,

затерявшимся

где-то

в

комитетах

по

стандартизации,

такое

использова­

ние аиto в настоящее время не допускается для аргументов функций.

Мы можем вызывать такую обобщенную функцию rota te_ and_ draw

()

с

любым

контейнером

объектов,

для

которых

допустимы

draw

()

и

rotate

().

Например:

void

user4 ()

{

 

 

vector<unique_ptr<Shape>>

 

vector<Shape*> v2;

 

11 ...

 

rotate_and_draw(vl,45);

 

rotate_and_draw(v2,90);

vl;

Используя

лямбда-выражение,

можно

превратить

любую инструкцию

в

выражение.

В

основном

это

используется

для

предоставления

операций

для

вычисления значения в

качестве аргумента, но эта

лее общей. Рассмотрим

сложную инициализацию:

способность

является

бо­

11 Варианты

инициализации:

enщn class

Init mode { zero,

seq,

еру,

patrn

};