пятница, 4 марта 2016 г.

Указатели на функции: объявление указателя на метод.

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

class A
{
public:

  void f()
  {
    x = 10;
  }

private:
  int x;
};

  Для объявления такого указателя надо лишь дописать на метод какого класса мы хотим ссылаться:

int main()
{
  void (A::*ptr)();
  return 0;
}

  Соответственно, чтобы присвоить такому указателю адрес одного из методов класса с подходящей сигнатурой, надо также явно указать имя класса.

int main()
{
  void (A::*ptr)() = &A::f;
  return 0;
}

  Как Вы помните из предыдущего урока, амперсанд перед именем функции в таком случае был опциональным. В случае с методом класса, он обязателен. Поэтому, для однообразности кода, я пишу его всегда.

  Наступил момент истины. Как же вызвать метод через такой указатель? Я задаю этот вопрос, так как обычного вызова, как мы делали до этого, недостаточно.

int main()
{
  void (A::*ptr)() = &A::f;
  ptr(); // compile error

  return 0;
}

  Как Вы видели из определения метода класса, его вызов должен выполнить операцию "x = 10;". У меня вопрос: "x" какого объекта класса мы собрались править? Ни одного объекта класса "A" не создано! Что ж, давайте создадим целых два. Один на стеке, другой в динамической памяти:


int main()
{
  A aOnStack;
  A* aInHeap = new A();

  void (A::*ptr)() = &A::f;
  ptr(); // compile error

  delete aInHeap;
  return 0;
}

  Снова неясно, чей же "x" изменится. Студенты часто предполагают, что изменится "x" последнего созданного объекта или всех созданных объектов, но это абсурдно. Для вызова метода класса через указатель, надо явно указать на каком объекте мы вызываем его:

int main()
{
  A aOnStack;
  A* aInHeap = new A();

  void (A::*ptr)() = &A::f;

  (aOnStack.*ptr)();
  (aInHeap->*ptr)();

  delete aInHeap;
  return 0;
}

  Имя класса также надо дописать и в typedef, если Вам захочется использовать указатели на методы через псевдонимы:
typedef void (A::*ptr_t)();


int main()
{
  A aOnStack;
  A* aInHeap = new A();

  ptr_t ptr = &A::f;

  (aOnStack.*ptr)();
  (aInHeap->*ptr)();

  delete aInHeap;

  return 0;
}


  Очень важно понимать, что вызов виртуального метода через указатель сработает как ожидается. Заодно обратите внимание на константность метода:
#include <iostream>

class A
{
public:
  virtual void f() const
  {
    std::cout << "A::f" << std::endl;
  }
};

class B : public A
{
public:
  void f() const override
  {
    std::cout << "B::f" << std::endl;
  }
};

typedef void (A::*ptr_t)() const;

int main()
{
  A* a = new B();

  ptr_t ptr = &A::f;
  (a->*ptr)();

  delete a;
  return 0;

}

  Выводом вышеприведенного кода будет "B::f". Вот так вот. Конструкции указателей на функции и методы одни из самых сложных в С++. Остается главный вопрос: зачем?

Комментариев нет:

Отправить комментарий