С++ для начинающих

       

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


Оператор “стрелка”, разрешающий доступ к членам, может перегружаться для объектов класса. Он должен быть определен как функция-член и обеспечивать семантику указателя. Чаще всего этот оператор используется в классах, которые предоставляют “интеллектуальный указатель” (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;

}

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


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