Урок 121. Скрытый указатель this

   ⁄ 

 Обновлено 5 Мар 2018  ⁄ 

⁄   1203

Один из частых вопросов, которые новички в ООП задают насчет классов: «При вызове метода класса, как C++ отслеживает какой объект его вызвал?». Ответ: C++ использует скрытый указатель this!

Ниже приведен простой класс, который содержит целочисленное значение и имеет конструктор и функции доступа. Обратите внимание, деструктор здесь не нужен, так как C++ может очистить переменную-член сам.

Вот пример программы, использующей этот класс:

Результат:

4

При вызове another.setNumber(4);, C++ понимает, что функция setNumber() работает с объектом another и m_number – это фактически another.m_number. Рассмотрим подробнее, как это всё работает.

Скрытый указатель *this

Возьмем, к примеру, строчку:

Хотя кажется, что у нас здесь только один аргумент, но на самом деле их два! Во время компиляции строчка another.setNumber(4); будет конвертирована компилятором в следующее:

Теперь это всего лишь стандартный вызов функции, а объект another (который ранее был отдельным объектом и находился перед точкой) теперь передается по адресу в качестве аргумента функции.

Но это только половина дела. Поскольку в вызове функции теперь два аргумента, то и метод нужно изменить соответствующим образом (чтобы он принимал два аргумента). Следовательно, следующий метод:

конвертируется компилятором в:

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



Есть еще одна деталь. Внутри метода также необходимо обновить все члены класса (функции и переменные), чтобы они ссылались на объект, который вызывает этот метод. Это легко сделать, добавив префикс «this->» к каждому из них. Таким образом, в теле функции setNumber(), m_number (переменная-член класса) будет конвертирована в this->m_number. И когда *this указывает на адрес another, то this->m_number будет указывать на another.m_number.

Объединяя всё вместе:

  При вызове another.setNumber(4), компилятор фактически вызывает setNumber(&another, 4).

  Внутри setNumber(), указатель *this содержит адрес объекта another.

  К любым переменным-членам внутри setNumber() добавляется префикс «this->». Поэтому, когда мы говорим m_number = number, компилятор фактически выполняет this->m_number = number, который в этом случае обновляет another.m_number в number.

Хорошей новостью является то, что это всё происходит скрыто от нас (программистов) и не имеет значения, помните ли вы, как это работает или нет. Всё, что вам нужно запомнить — все обычные методы класса имеют указатель *this, который указывает на объект связанный с вызовом метода класса.

*this всегда указывает на текущий объект

Начинающие программисты иногда путаются с тем, сколько указателей *this существует. Каждый метод имеет параметр — указатель *this, который указывает на адрес объекта с которым в данный момент выполняется операция. Например:

Обратите внимание, указатель *this поочередно содержит адрес объекта X или Y в зависимости от того, какой метод вызван и сейчас выполняется.

Явное указание *this

В большинстве случаев вам не нужно явно указывать *this. Тем не менее, иногда это может быть полезным:

Во-первых, если у вас есть конструктор (или метод), который имеет параметр с тем же именем, что и переменная-член, то устранить неоднозначность можно с помощью «this»:

Здесь конструктор принимает параметр с тем же именем, что и переменная-член. В этом случае «data» относится к параметру, а «this->data» относится к переменной-члену. Хотя это приемлемая практика, но мы рекомендуем использовать префикс «m_» для всех имен переменных-членов вашего класса, так как это помогает предотвратить дублирование имен в целом!

Цепочки методов класса

Во-вторых, иногда бывает полезно, чтобы метод класса возвращал объект, с которым работает, в виде возвращаемого значения. Основной смысл — позволить нескольким методам объединиться в «цепочку», работая при этом с одним объектом! Мы на самом деле пользуемся этим уже давно. Например, когда мы выводим несколько частей данных с помощью std::cout:

В этом случае std::cout является объектом, а оператор << — методом, который работает с этим объектом. Компилятор обрабатывает фрагмент выше следующим образом:

Сначала оператор << использует std::cout и строковый литерал «Hello» для вывода «Hello» в консоль. Однако, поскольку это часть выражения, оператор << также должен возвратить значение (или void). Если оператор << возвращает void, то получается следующее:

что явно не имеет никакого смысла (и компилятор выдаст ошибку). Однако вместо этого оператор << возвращает *this, что в этом контексте является просто std::cout. Таким образом, после обработки первого оператора <<, мы получаем:

что приводит к выводу имени пользователя (userName).

Таким образом, нам нужно указать объект (в данном случае, std::cout) один раз, и каждый вызов функции будет передавать этот объект следующей функции, что позволит нам объединить несколько методов вместе.

Мы сами можем реализовать такое поведение. Рассмотрим следующий класс:

Если вы хотите добавить 7, вычесть 5 и умножить всё это на 3, то нужно будет сделать следующее:

Результат:

6

Однако, если каждая функция будет возвращать *this, то мы сможем связать эти вызовы методов в одну строчку. Например:

Обратите внимание, add(), sub() и multiply() теперь возвращают *this. Поэтому, следующее будет корректным:

Результат:



6

Мы фактически вместили три строчки в одно выражение! Теперь рассмотрим это детальнее:

  сначала вызывается operation.add(7), который добавляет 7 к нашему m_value;

  затем add() возвращает *this, что является ссылкой на объект operation;

  затем вызов operation.sub(5) вычитает 5 из m_value и возвращает operation;

  multiply(3) умножает m_value на 3 и возвращает operation, который уже игнорируется;

  однако, поскольку каждая функция модифицировала operation, то m_value объекта operation теперь содержит значение ((0 + 7) — 5) * 3), которое равно 6.

Итого

Указатель *this является скрытым параметром, который неявно добавляется к каждому методу класса. В большинстве случаев нам не нужно обращаться к нему напрямую, но при необходимости это можно сделать. Стоит отметить, что this является константным указателем — вы можете изменить значение исходного объекта, но вы не можете указать this указывать на что-то другое!

Если у вас есть функции, которые возвращают void – возвращайте *this вместо void, таким образом вы сможете соединять несколько методов в одну «цепочку». Это чаще всего используется при перегрузке операторов в классах (об этом детальнее поговорим в главе 9).

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (10 оценок, среднее: 5,00 из 5)
Загрузка...
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО