Как решается указанная задача в объектно-ориентированном дизайне? Объявляется базовый абстрактный класс, в котором описывается интерфейс в виде набора чисто виртуальных методов. Новый тип создается путем создания наследуемого класса, в котором объявляются нужные переменные и переопределяются методы. При инициализации создается класс нужного типа, и он сохраняется в переменной – указателе на базовый класс. Мы будем использовать аналогичный подход, только наследуемые типы будут создаваться динамически, используя параметры шаблона. Указанная техника называется «стирание типов»: при назначении нового типа аргумента предыдущий сохраненный уничтожается, и его место занимает новый22.

Графическое изображение стирания типов изображено на Рис. 17. Рассмотрим начальное состояние а), показанное в верхней части рисунка. Имеется некоторый класс, назовем его UniArgument. В этом классе объявлен перегруженный оператор вызова функции 2. Также здесь имеется указатель 3 типа Callable*, который указывает на соответствующий экземпляр класса Callable. Класс Callable 4 объявлен внутри UniArgument и имеет виртуальный перегруженный оператор вызова функции с пустой реализацией.

Когда в UniArgument происходит вызов 1 перегруженного оператора 2, последний через указатель 3 вызывает виртуальный перегруженный оператор класса Callable.

В нижней части рисунка б) показано, как назначается новый тип. Объявляется перегруженный оператор присваивания 10, на входе он принимает аргумент обратного вызова 8. При вызове этого оператора старый экземпляр класса 4, на который указывал указатель 3, уничтожается в 11, а вместо него создается новый класс CallableObject 5, который наследуется от Callable. Внутри класса имеется поле 7, в которое записывается переданный аргумент 8, тип этого поля совпадает с типом аргумента. В CallableObject переопределяется оператор вызова функции 6, который, в свою очередь, осуществляет вызов через сохраненный аргумент 7. Теперь указатель 3 указывает на новый созданный CallableObject, и при вызове 1 перегруженного оператора 2 будет вызываться перегруженный оператор указанного класса, который и выполнит обратный вызов.

Рис. 17. Стирание типов: а) исходное состояние; б) состояние после назначения нового типа аргумента

Реализация рассмотренной схемы представлена в Листинг 45.

Листинг 45. Класс, реализующий стирание типов

class UniArgument  // (1)

{

private:

  class Callable   // (2)

  {

  public:

    virtual void operator()(int) = 0;         // (3)

  };

  std::unique_ptr callablePointer;  // (4)

  template 

  class CallableObject : public Callable      // (5)

  {

  public:

    CallableObject(ArgType argument) : storedArgument(argument) { }  // (6)

    void operator() (int value) override  // (7)

    {

      storedArgument(value);  // (8)

    }

  private:

    ArgType storedArgument;   // (9)

  };

public:

  void operator() (int value)  // (10)

  {

    callablePointer->operator()(value);  // (11)

  }

  template 

  void operator = (ArgType argument)  // (12)

  {

    callablePointer.reset(new CallableObject(argument));  // (13)

  }

};

В строке 1 объявлен класс, реализующий универсальный аргумент. В строке 2 объявлен класс, который будет использоваться в качестве базового.

В базовом классе перегружен оператор вызова функции 3. Оператор объявлен чисто виртуальным, чтобы опустить его реализацию. Предполагается, что этот оператор будет выполнять обратный вызов, но аргумента вызова здесь нет, он будет храниться в наследуемом классе. Таким образом, реализация смысла не имеет. Более того, если, допустим, нам понадобится, чтобы оператор возвращал результат, то в нем должна присутствовать команда return, и какое тогда возвращать значение?

В строке 4 объявлен указатель на базовый класс, объявленный в 2.

В строке 5 объявлен шаблонный класс, который будет хранить переданный аргумент и вызывать его. Переменная для хранения аргумента объявлена в строке 9, тип переменной задается параметром шаблона. Аргумент назначается в конструкторе 6. Также в этом классе переопределяется оператор вызова функции 7, в котором происходит обратный вызов 8 через сохраненный аргумент.

Перейти на страницу:

Похожие книги