Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Липпман.doc
Скачиваний:
8
Добавлен:
14.08.2019
Размер:
7.54 Mб
Скачать

15.6. Оператор “стрелка”

Оператор “стрелка”, разрешающий доступ к членам, может перегружаться для объектов класса. Он должен быть определен как функция-член и обеспечивать семантику указателя. Чаще всего этот оператор используется в классах, которые предоставляют “интеллектуальный указатель” (smart pointer), ведущий себя аналогично встроенным, но поддерживают и некоторую дополнительную функциональность.

Допустим, мы хотим определить тип класса для представления указателя на объект Screen (см. главу 13):

class ScreenPtr {

// ...

private:

Screen *ptr;


};

Определение ScreenPtr должно быть таким, чтобы объект этого класса гарантировано указывал на объект Screen: в отличие от встроенного указателя, он не может быть нулевым. Тогда приложение сможет пользоваться объектами типа ScreenPtr, не проверяя, указывают ли они на какой-нибудь объект Screen. Для этого нужно определить класс ScreenPtr с конструктором, но без конструктора по умолчанию (детально конструкторы рассматривались в разделе 14.2):

class ScreenPtr {

public:

ScreenPtr( const Screen &s ) : ptr( &s ) { }

// ...


};

В любом определении объекта класса ScreenPtr должен присутствовать инициализатор – объект класса Screen, на который будет ссылаться объект ScreenPtr:

ScreenPtr p1; // ошибка: у класса ScreenPtr нет конструктора по умолчанию

Screen myScreen( 4, 4 );


ScreenPtr ps( myScreen ); // правильно

Чтобы класс ScreenPtr вел себя как встроенный указатель, необходимо определить некоторые перегруженные операторы – разыменования (*) и “стрелку” для доступа к членам:

// перегруженные операторы для поддержки поведения указателя

class ScreenPtr {

public:

Screen& operator*() { return *ptr; }

Screen* operator->() { return ptr; }

// ...


};

Оператор доступа к членам унарный, поэтому параметры ему не передаются. При использовании в составе выражения его результат зависит только от типа левого операнда. Например, в инструкции

point->action();

исследуется тип point. Если это указатель на некоторый тип класса, то применяется семантика встроенного оператора доступа к члену. Если же это объект или ссылка на объект, то проверяется, есть ли в этом классе перегруженный оператор доступа. Когда перегруженный оператор “стрелка” определен, он вызывается для объекта point, иначе инструкция неверна, поскольку для обращения к членам самого объекта (в том числе по ссылке) следует использовать оператор “точка”.

Перегруженный оператор “стрелка” должен возвращать либо указатель на тип класса, либо объект класса, в котором он определен. Если возвращается указатель, то к нему применяется семантика встроенного оператора “стрелка”. В противном случае процесс продолжается рекурсивно, пока не будет получен указатель или определена ошибка. Например, так можно воспользоваться объектом ps класса ScreenPtr для доступа к членам Screen:

ps->move( 2, 3 );

Поскольку слева от оператора “стрелка” находится объект типа ScreenPtr, то употребляется перегруженный оператор этого класса, который возвращает указатель на объект Screen. Затем к полученному значению применяется встроенный оператор “стрелка” для вызова функции-члена move().

Ниже приводится небольшая программа для тестирования класса ScreenPtr. Объект типа ScreenPtr используется точно так же, как любой объект типа Screen*:

#include <iostream>

#include <string>

#include "Screen.h"

void printScreen( const ScreenPtr &ps )

{

cout << "Screen Object ( "

<< ps->height() << ", "

<< ps->width() << " )\n\n";

for ( int ix = 1; ix <= ps->height(); ++ix )

{

for ( int iy = 1; iy <= ps->width(); ++iy )

cout << ps->get( ix, iy );

cout << "\n";

}

}

int main() {

Screen sobj( 2, 5 );

string init( "HelloWorld" );

ScreenPtr ps( sobj );

// Установить содержимое экрана

string::size_type initpos = 0;

for ( int ix = 1; ix <= ps->height(); ++ix )

for ( int iy = 1; iy <= ps->width(); ++iy )

{

ps->move( ix, iy );

ps->set( init[ initpos++ ] );

}

// Вывести содержимое экрана

printScreen( ps );

return 0;


}

Разумеется, подобные манипуляции с указателями на объекты классов не так эффективны, как работа со встроенными указателями. Поэтому интеллектуальный указатель должен предоставлять дополнительную функциональность, важную для приложения, чтобы оправдать сложность своего использования.