Иллюстрированный самоучитель по Visual Studio.Net

         

Связыватели и адаптеры


* Связывателями (binders) называются вспомогательные шаблоны функций, которые создают некий объект (adaptor) , подстраивающий или преобразующий бинарный функциональный объект в унарный путем привязывания недостающего аргумента. Звучит запутанно, но суть достаточно проста. Представьте, что надо найти в нашей последовательности людей первого человека, который моложе, чем

Man win("Winton Kelly", 50);

Для объектов класса Man уже определена бинарная операция operator< (), которой пользуется предикат less<Man> (), и мы показали использование этого предиката в алгоритме сортировки по возрасту. В том примере функция sort сама подставляла оба параметра в бинарную функцию operator< (), сравнивая объекты для нужд сортировки. Теперь мы используем связыватель bind2nd, для того чтобы зафиксировать (привязать) второй параметр этой функции и сделать его равным объекту win. Первый параметр при этом остается свободным, он будет пробегать по всему контейнеру. Таким образом, мы сможем сравнить все объекты последовательности с одним фиксированным объектом win.

В этом же фрагменте мы покажем, как использовать другой адаптер mem_f un_ref, который тоже является вспомогательным шаблоном функции для вызова какой-либо функции, являющейся членом класса, в нашем случае Man. Вызов осуществляется для всех объектов класса в процессе прохода по контейнеру. Введите в состав класса Man две public-функции, выделяющие имя и фамилию человека. В коде этих функций попутно демонстрируются методы класса string, которые позволяют осуществлять поиск и выделение подстроки:

//======== Выделяем имя

Man FirstName()

{

//======== Ищем первое вхождение пробела

int pos = m_Name.find_first_of(string(" "),0);

string name = m_Name.substr(0, pos);

cout « '\n' « name;

return *this;

}

//======== Выделяем фамилию

Man SurName()



{

//======== Ищем последнее вхождение пробела

int pos = m_Name.find_last_of(" "), num = m_Name.length () - pos;

string name = m_Name.substr(pos + 1, num);



cout « '\n' « name; return *this;

}

Вектор заполняется элементами, взятыми из массива а г, и при этом используется метод assign, который стирает весь массив и вновь заполняет его элементами, копируя их из диапазона памяти, задаваемого параметрами. Далее мы показываем, как используется связыватель bind2nd и адаптер члена-функции mem_f un_ref:

void main ()

{

Man ar[] =

{

joy, joe, zoran, тагу, simon, liza, Man("Lina Groves", 19)

};

uint size = sizeof(ar)/sizeof(Man);

vector<Man> men;

men.assign(ar, ar+size);

pr(men,"Man Vector");

//======= Привязка второго аргумента

vector<Man>::iterator p = find_if(men.begin(),

men.end(), bind2nd(less<Man>(), win));

if (p != men.end())

cout « "\nFound a man less than " « win « "\n\t" « *p;

//======= Использование метода класса (mem_fun_ref)

cout « "\n\nMen Names:\n";

for_each (men.begin(), men.end(), mem_fun_ref(&Man::SurName));

cout « "\n\nMen First Names:\n";

for_each (men.begin (), men.end(), mem_fun_ref(&Man::FirstName));

cout « "\n\n";

}

Напомним, что для успешной работы вы должны вставить в функцию main тот набор объектов класса Man, который был приведен ранее.

Примечание

При анализе этого кода бросается в глаза неестественность прототипов функций SurName и FirstName. Логика использования этих функций совсем не требует возвращать какое-либо значение, будь то Man, или переменная любого другого типа. Естественным выбором будет прототип void SurNameQ;. Но, к сожалению, этот выбор не проходит по неизвестным мне причинам ни в Visual Studio б, ни в Studio.Net 7.O. Я достаточно много времени потратил на бесполезные поиски ответа на этот вопрос и пришел к выводу, что это ошибка разработчиков. В подтверждение такого вывода приведу следующие аргументы. Во-первых, измените тип возвращаемого значения на любой другой, но не void, и программа будет работать. Например, возьмите прототип string SurName(); и возвращайте return "MicrosoftisOK"; (или другую пару: int и-127).


Во-вторых, все примеры на (mem_fun_ref) в документации MSDN возвращают загадочный bool. В-третьих, в документации SGI (Silicon Graphics) приведены аналогичные примеры с функциями, возвращающими void. Там, как вы знаете, используется другая платформа (IRIS). В-четвертых, наш пример (без void) проходит в Visual Studio б и не работает в бета-версии Studio.Net. Будем надеяться, что ситуация со временем выправится.



Адаптер mem_fun в отличие от mem_fun__ref используется с контейнерами, хранящими указатели на объекты, а не сами объекты. Хорошим примером использования mem_f un, в котором иллюстрируется полиморфизм позднего связывания (late binding polymorphism), является следующий:

//======== Базовый класс. К сожалению, абстрактным

//======= его не позволит сделать контейнер

struct Stud

virtual bool print()

{

cout « "\nl'm a Stud";

return true;

}

};

//===== Производный класс struct GoodStud : public Stud

{

bool print ()

{

cout « "\nl'm a Good Stud";

return true;

}

};

//======= Еще один производный класс

struct BadStud : public Stud

{

bool print ()

{

cout « "XnI'm a Bad Stud";

return true;

}

};

//======= Иллюстрируем полиморфизм в действии

void main () {

//====== Вектор указателей типа Stud*

vector<Stud*> v;

//====== Они могут указывать и на детей

v.push_back (new StudO);

v.push_back (new GoodStudO);

v.push_back(new BadStud(J);

//====== Выбор тела метода происходит поздно

//====== на этапе выполнения

for_each(v.begin(), v.end(), mem_fun(&Stud:: print));

cout <<"\n\n";

}

Конечно же, эта программа выведет:

I'm a Stud

I'm a Good Stud

I'm a Bad Stud

так как mem_fun будет вызвана с помощью указателя типа stud* (на базовый класс) — непременное условие проявления полиморфизма, то есть выбора конкретной версии виртуальной функции (адреса функции из vtable) на этапе выполнения. Выбор определяется конкретной ситуацией — типом объекта, попавшим под родительский перст (указатель) в данный момент времени.




Содержание раздела