Как обратиться к методу класса с

от admin

Как вызвать определённый метод из другого класса?

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

Это вообще возможно реализовать в C#? Если да, то как?

Если предполагается хранить методы, которые не принимают аргументов и ничего не возвращают, то будет достаточно Action :

Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.3.11.43300

Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.

Классы, структуры и пространства имен

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

Описанием объекта является класс , а объект представляет экземпляр этого класса. Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке, у которого есть имя, возраст, какие-то другие характеристики. То есть некоторый шаблон — этот шаблон можно назвать классом. Конкретное воплощение этого шаблона может отличаться, например, одни люди имеют одно имя, другие — другое имя. И реально существующий человек (фактически экземпляр данного класса) будет представлять объект этого класса.

В принципе ранее уже использовались классы. Например, тип string , который представляет строку, фактически является классом. Или, например, класс Console , у которого метод WriteLine() выводит на консоль некоторую информацию. Теперь же посмотрим, как мы можем определять свои собственные классы.

По сути класс представляет новый тип, который определяется пользователем. Класс определяется с помощью ключевого слова сlass :

После слова class идет имя класса и далее в фигурных скобках идет собственно содержимое класса. Например, определим в файле Program.cs класс Person, который будет представлять человека:

Классы и объекты в языке программирования C# и .NET

Однако такой класс не особо показателен, поэтому добавим в него некоторую функциональность.

Поля и методы класса

Класс может хранить некоторые данные. Для хранения данных в классе применяются поля . По сути поля класса — это переменные, определенные на уровне класса.

Кроме того, класс может определять некоторое поведение или выполняемые действия. Для определения поведения в классе применяются методы.

Итак, добавим в класс Person поля и методы:

В данном случае в классе Person определено поле name , которое хранит имя, и поле age , которое хранит возраст человека. В отличие от переменных, определенных в методах, поля класса могут иметь модификаторы, которые указываются перед полем. Так, в данном случае, чтобы все поля были доступны вне класса Person поля определены с модификатором public .

При определении полей мы можем присвоить им некоторые значения, как в примере выше в случае переменной name . Если поля класса не инициализированы, то они получают значения по умолчанию. Для переменных числовых типов это число 0.

Также в классе Person определен метод Print() . Методы класса имеют доступ к его поля, и в данном случае обращаемся к полям класса name и age для вывода их значения на консоль. И чтобы этот метод был виден вне класса, он также определен с модификатором public .

Создание объекта класса

После определения класса мы можем создавать его объекты. Для создания объекта применяются конструкторы . По сути конструкторы представляют специальные методы, которые называются так же как и класс, и которые вызываются при создании нового объекта класса и выполняют инициализацию объекта. Общий синтаксис вызова конструктора:

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

Конструктор по умолчанию

Если в классе не определено ни одного конструктора (как в случае с нашим классом Person), то для этого класса автоматически создается пустой конструктор по умолчанию, который не принимает никаких параметров.

Теперь создадим объект класса Person:

создание классов в языке программирования C# и .NET

Для создания объекта Person используется выражение new Person() . В итоге после выполнения данного выражения в памяти будет выделен участок, где будут храниться все данные объекта Person. А переменная tom получит ссылку на созданный объект, и через эту переменную мы можем использовать данный объект и обращаться к его функциональности.

Обращение к функциональности класса

Для обращения к функциональности класса — полям, методам (а также другим элементам класса) применяется точечная нотация точки — после объекта класса ставится точка, а затем элемент класса:

Например, обратимся к полям и методам объекта Person:

Консольный вывод данной программы:

Добавление класса

Обычно классы помещаются в отдельные файлы. Нередко для одного класса предназначен один файл. Если мы работаем над проектом вне среды Visual Studio, используя .NET CLI, то нам достаточно добавить новый файл класса в папку проекта. Например, добавим новый файл, который назовем Person.cs и в котором определим следующий код:

Здесь определен класс Person с одним полем name и методом Print.

В файле Program.cs , который представляет основной файл программы используем класс Person:

Использование классов в проекте в Visual Studio в языке программирования C#

Visual Studio предоставляет по умолчанию встроенные шаблоны для добвления класса. Для добавления класса нажмем в Visual Studio правой кнопкой мыши на название проекта:

Добавление класса в Visual Studio в C#

В появившемся контекстном меню выберем пункт Add -> New Item. (или Add -> Class. )

В открывшемся окне добавления нового элемента убедимся, что в центральной части с шаблонами элементов у нас выбран пункт Class . А внизу окна в поле Name введем название добавляемого класса — пусть он будет назваться Person :

Добавление нового класса в Visual Studio в C#

В качестве названия класса можно вводить как Person, так и Person.cs. И после нажатия на кнопку добавления в проект будет добавлен новый класс, в котором можно определить тот же код и также использовать в файле Program.cs.

Таким образом, мы можем определять классы в отдельных файлах и использовать их в программе.

Указатели на методы классов в C++

Решил написать статью об указателях на методы классов. Недавно мне пришлось столкнуться с тем, как они работают изнутри, когда писал некоторые вещи ориентированные под компилятор. Эти указатели работают не совсем как обычные указатели, не имеют возможности быть приведенными в void, и часто имеют размер больше 8 байт. Информации на эту тему в интернете я нашел относительно немного, потому решил разобраться сам.

Особенно пугают такие страшилки, которые мало что объясняют о том как происходит на самом деле и почему, а лишь пытаются приучить программиста слепо следовать требованиям.

Давайте разберемся что и почему происходит.
Все манипуляции будут произведены для архитектуры x86-64.

Взглянем на код.

Размер указателя на метод больше 8 байт. В некоторых компиляторах это не так, например компилятор Microsoft ужимает до 8 байт указатель на метод в некоторых случаях. В последних версиях компиляторов clang и gcc для Linux принимал размер 16 байт.

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

Посмотрим такой код на C++. Это базовый пример вызова метода из указателя на метод.

Скомпилировав код такой командой:

Получаем вывод LLVM IR:

LLVM IR является промежуточным представлением между машинным кодом и C++ в компиляторе Clang. Он позволяет компилятору производить оптимизации не зависящие от конкретной архитектуры процессора, а нам он дает понять что происходит на тех или иных стадиях компиляции, и является более читаемым чем язык ассемблера.

Подробнее про LLVM IR можно узнать в Википедии, официальном сайте LLVM и Clang.

  • Взглянув на первую строчку, видно что указатель на метод является структурой `< i64, i64 >`, а не обычным указателем. Эта структура содержит два i64 элемента, которые могут уместить в себя 2 обычных указателя. Видно почему мы не можем приводить указатели на методы в обычные. Мы не можем без потерь преобразовать 16 байт в 8 байт в общем случае.
  • В блоке `entry`, начинающимся с 5 строки, видно что происходит корректирование указателя `this`. Это значит, что компилятор прибавляет к указателю на `this` значение второго элемента этой структуры, и позже в блоке `memptr.end` передает его в вызов метода.
  • Нечто странное происходит происходит в блоке `entry` на 14 строке с первым элементом структуры. Компилятор вычисляет выражение аналогичное следующему: `bool isvirtual = val & 1`. Компилятор считает указатель на метод виртуальным, если число в нем нечетное, в противном случае невиртуальным.
  • Если указатель на метод указывает на невиртуальный метод, то значение первого элемента считается обычным указателем на функцию, который позже вызывается. Эти предположения происходят в блоке `memptr.nonvirtual`.
  • Если указатель на метод указывает на виртуальный метод, то тут сложнее. Вначале вычитается единица из первого элемента структуры, и вычисленное значение является отступом для виртуальной таблицы, указатель на которую берется из значения указателя на `this`. Это происходит в блоке `memptr.virtual`.
  • Информацию является ли он виртуальным
  • Указатель на адрес метода (если не виртуальный)
  • Смещение в vtable (если виртуальный)
  • корректирование `this`

Метод класса имеет невидимый первый параметр — указатель на `this`, который передается компилятором при вызове метода. Остальные аргументы передаются после в том же порядке, что и были.

Если бы мы писали этот код на C++, то он выглядел бы примерно так:

Чтобы разобраться с значением корректирования, рассмотрим следующий пример:

Как мы видим, тут представлены примеры указателей на методы и аналогичные функции, которые принимают указатель на класс как указатель на `this`. Однако компилятор не может преобразовать указатели a1 и b1 в связи с тем, что мы не можем бесплатно преобразовывать указатели дочернего типа в указатели родительского типа. Компилятору необходимо запомнить отступ (значение корректирования) внутри дочернего класса для родительского класса и сохранить его где-то.

Посмотрим такой код:

Скомпилируем код командой:

Видно, что указатель на метод указывает на одну и ту же функцию. Однако значение корректирования разное из-за того что класс B расположен по сути внутри класса C.
Компилятору C++ необходимо знать отступ от базового класса для того чтобы передать `this` в метод класса.

Что плохого в этой реализации:

  • Размер указателя относительно большой, даже если корректирование отсутствует каким либо образом в gcc и clang
  • Каждый раз идет проверка виртуальности метода, даже если мы знаем что он не виртуальный
  • Использовать статический метод, принимающий экземпляр класса
  • Забыть про существование указателей на методы, и решить проблему как-то иначе в прочих случаях
  • В интернете есть советы использовать std::bind, std::function и подобные библиотечные функции. Проверив их поведение, я не обнаружил существования каких либо оптимизаций для указателей на методы.
  • У меня нет технической возможности проверить что происходит в компиляторах Microsoft, поэтому не особо про них рассказал. Однако протестировав онлайн компиляторы, я заметил что MSVC умеет анализировать структуру классов и удалять поле значения корректирования, если оно не требуется.

В данном примере компилятор не будет проверять метод на виртуальность, и сразу вызовет его как невиртуальный. Это лишь пример необычной оптимизации, не стоит использовать его в реальности.

Также я написал маленькую программку, что выводит данные об указателях на методы и помогает понять их внутренности, пока писал эту статью. Работает в clang и gcc под Linux. Код выложен тут.

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

Как обратиться к методу класса с

Как обратиться к определенной колонке ТЗ и к определенной строке из этой колонки?
Как обратиться к определенной колонке ТЗ и к определенной строке из этой колонки? Добавлено.

Как обратиться к методу класса из внешней функции?
Имеется простой класс Employee с методами GetAge() — возвращает возраст, и SetAge(int age). Как.

Как из другой функции обратиться к членам класса окна?
Здравствуйте! Вобщем в VC6 у меня есть класс окна, зовется CMyDlg. Так вот по нажатию на кнопку.

Как обратиться к функции класса, который находится в листе?
Как обратиться к функции класса, который находится в листе? В классе есть функция Move. .

Смотря из какого класса нужно обратиться к методу (функций и процедур в плане терминологии в шарпе нет).

Если из того же класса, в котором метод объявлен, то очень просто:

Сообщение от kolorotur

Вот в чем оказывается ошибка была.

Возник следующий вопросик (не стал создавать новую тему решил здесь спросить)

Есть есть публичный класс MvcApplication со статичным публичным методом GetUnicName

мне компилятор говорит следующее:
Error 4 The name ‘MvcApplication’ does not exist in the current context

Классы находятся в одном Solution (естественно))), но в разных проектах

Подскажите в чем проблема

jaguar7021, а у вас класс который вызываете и из которого вызываете в одном namespace?

turbanoff, опередил

Вот такой ошибкой:
Error 2 The type or namespace name ‘WebFront’ could not be found (are you missing a using directive or an assembly reference?)

И вызов на прямую тоже не работает:

Вот такая ошибка:
Error 4 The name ‘WebFront’ does not exist in the current context

Добавлено через 1 час 2 минуты
все разобрался, спасибо. Там оказалось что один проект ссылается на другой, а я этими действиями пытался их зациклить между собой)).

Перенес в другой файл функцайку

Как обратиться к определенной кнопке
У меня имеется таблица, созданная циклом в PHP, и крайний столбец отведен под кнопку &quot;удалить&quot;. Как.

Как в таблице обратиться к определенной колонке
Есть табл fxml. Колонки: Firstname, Lastname, Phone, Email Я хочу сделать слушатель на колонку.

Не могу обратиться к методу класса в функции
Добрый день. При написании программы возникла проблема — в функции не могу обратиться к методу.

Как обратиться из конструктора базового абстрактного класса к свойству-массиву класса наследника
Добрый день. Подскажите как обратиться из конструктора базового абстрактного класса к.

Как обратиться к полю объекта класса Car, который находится в массиве класса Cars?
Помогите пожалуйста разобраться. Ниже три разных модуля. Как обратиться к полю объекта класса Car.

Как обратиться к свойству класса из метода этого же класса
Добрый день! function Tta_component.check: boolean; //âåðíåò true åñëè ïðè èíèöèàëèçàöèè.

Как обратиться к методу класса с

Эта статья даст базовое понимание терминов «класс», «метод», «наследование», «перегрузка метода»

Читать:
Как подключить телефон avaya к сети

Содержание

Методы

Методы — это функции, объявление которых размещено внутри определения класса или структуры. В список переменных, доступных для метода, неявно попадают все поля структуры или класса, в котором он объявлен. Другими словами, в список областей видимости метода попадает область видимости структуры.

Взгляните на пример:

Методу Vec2f::getLength доступны все символы (т.е. переменные, функции, типы данных), которые были объявлены в одной из трёх областей видимости. При наличии символов с одинаковыми идентификаторами один символ перекрывает другой, т.к. поиск происходит от внутренней области видимости к внешней.

Понять идею проще на схеме. В ней область видимости названа по-английски: scope.

Схема

Поднимаясь по схеме от внутренней области видимости к внешней, легко понять, какие имена символов доступны в методе getLength:

  1. локальная переменная “lengthSquare”
  2. поля Vec2f под именами “x” и “y”
  3. всё, что есть в глобальной области видимости

К слову, в других методах структуры Vec2f переменная “lengthSquare” будет недоступна, а поля “x” и “y” будут доступны.

Конструкторы

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

Посмотрите на простой пример. В нём есть проблема: и поля, и параметры конструктора названы одинаково. В результате в области видимости конструктора доступны только параметры, и своими именами они перекрывают поля!

Язык C++ предлагает два решения. Первый способ — использовать косвенное обращение к полям через привязанный к методу объект. Указатель на него доступен по ключевому слову this :

Второй путь считается более правильным: мы используем специальную возможность конструкторов — “списки инициализации конструктора” (англ. constructor initializer lists). Списки инициализации — это список, разделённый запятыми и начинающийся с “:”. Элемент списка инициализации выглядит как field(expression) , т.е. для каждого выбранного программистом поля можно указать выражение, инициализирующее его. Имя переменной является выражением. Поэтому мы инициализируем поле его параметром:

Объявление и определение методов

C++ требует, чтобы каждый метод структуры или класса был упомянут в определении этой структуры или класса. Но допускается писать лишь объявление метода, о определение размещать где-нибудь в другом месте:

Классы и структуры

В C++ есть ключевое слово class — это практически аналог ключевого слова struct . Оба ключевых слова объявляют тип данных, и разница между ними есть только на стыке наследования и инкапсуляции. Других различий class и struct не существует.

Основы инкапсуляции

В C++ можно блокировать доступ к полям извне, но сохранять доступ для методов. Для этого введены три области доступа

  1. public — символ в этой области доступен извне
  2. private — символ из этой области доступен лишь собственных в методах
  3. protected — используется редко, о нём можете прочитать в документации

Давайте сделаем поля типа Vec2f недоступными извне. Также мы заменим ключевое слово struct на class — это не меняет смысла программы, но считается хорошим тоном использовать struct только если все поля доступны публично.

Запомните несколько хороших правил:

  • Используйте struct, если все поля публичные и не зависят друг от друга; используйте class, если между полями должны соблюдаться закономерности (например, поле “площадь” круга должно быть)

Основы наследования

В C++ новый тип может наследовать все поля и методы другого типа. Для этого достаточно указать структуру или класс в списке базовых типов. Такой приём используется в SFML при объявлении классов фигур:

Что означает public перед именем базового типа? Во-первых внешний код может передать RectangleShape в функцию, принимающую ссылку на Shape, то есть возможен так называемы upcast от более низкого (и более конкретного) типа RectangleShape к более высокому (и более абстрактному) типу Shape:

Во-вторых из-за public наследования все унаследованные поля и методы сохраняют свой уровень доступ: приватные остаются приватными, публичные остаются публичными. А если бы мы наследовали Shape с ключевым словом private, то уровень доступа стал бы ниже: все методы и поля стали бы приватными:

Контроль уровня доступа полей и методов — хитрый механизм, пройдёт немало времени, прежде чем вы научитесь пользоваться им правильно. В начале просто старайтесь сделать правильный выбор между private и public. Скорее всего поля будут private, а конструктор и все методы будут public. Это позволяет сохранять инвариант класса, то есть держать поля объекта в согласованном состоянии независимо от того, какие методы вызывают извне.

Основы полиморфизма: виртуальные методы и их перегрузка

SFML использует ещё одну идиому C++: виртуальные методы. Ключевые слова virtual , final , override относятся именно к этой идиоме. Например, в SFML определяется класс Drawable, который обозначает “сущность, которую можно нарисовать”. Все рисуемые классы SFML, включая sf::Sprite , sf::RectangleShape , sf::Text , прямо или косвенно наследуются от sf::Drawable .

Зачем это надо? Дело в том, что метод draw класса RenderWindow принимает параметр типа Drawable . Тем не менее, этот метод успешно рисует любые типы объектов: спрайты, фигуры, тексты. Он не выполняет проверок — он просто настраивает состояние рисования (RenderStates) и вызывает метод draw у сущности, которая является Drawable .

Виртуальный метод вызывается косвенно: если класс Shape , унаследованный от Drawable , переопределил метод, а потом был передан как параметр типа Drawable , то вызов метода draw всё равно приведёт к вызову переопределённого метода Shape::draw , а не метода Drawable::draw ! С обычными (не виртуальными) методами такого не происходит: если бы мы убрали слово virtual из объявления draw , то вызов метода draw у параметра типа Drawable всегда приводил бы к вызову Drawable::draw , даже если реальный тип объекта, скрытого за этим параметром, совсем другой.

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

Другими словами, RenderWindow и RectangleShape не знают, что они работают друг с другом, но тем не менее каждый вызывает правильный метод другого класса!

Иллюстрация

Когда вы просто вызываете window.draw(shape) , повышение класса происходит дважды: сначала конкретный класс фигуры повышается до более ограниченного класса Drawable, затем конкретный класс RenderWindow повышается до абстрактного RenderTarget. Всё это не требует времени при выполнении: просто компилятор выполняет проверки типов данных ещё при компиляции, не более того.

Как унаследовать Drawable: практический пример

Мы создадим класс, который рисует флаг России. Он будет унаследован от Drawable, чтобы использовать для рисования обычный метод draw у объекта окна.

Теперь мы можем реализовать конструктор и метод draw. В конструкторе мы должны вычислить и установить позиции и размеры трёх полос на флаге, а в методе draw мы должны их последовательно нарисовать.

Как обратиться к методу класса с

Строка: Form1.Metod("Этот текст из стороннего обьекта") — вызывает ошибку:

An object reference is required for the non-static field, method, or property ‘WindowsFormsApplication1.Form1.Metod(string)’

делать статическим — нежелательно. Этот вариант уже испробован, возможно какие нибудь есть другие варианты? Как обратиться к методам и полям Form1 из другого обьекта?

Ответы

1. передать делегат в конструктор Klass1 (от NIKkakNIK)

2. передать ссылку на Form1 целиком в конструктор Klass1.

3. добавить в Klass1 событие (event) и в коде Form1 подписаться на него.

4. вытащить ссылку на форму из System.Windows.Forms.Application.OpenForms[]

  • Помечено в качестве ответа Abolmasov Dmitry 14 марта 2011 г. 12:54

Спасибо за ответ. Но тут же возникает вопрос — как я понимаю, при запуске программы, уже создается экземпляр Form1. А если я создам новый объект, то строка: this.textBox1.Text = s; эта операция будет применена к новому обьекту и не будет иметь с существующей начальной Form1 — ничего общего — элемент textBox1 — не изменит своего значения в начальной Form1.

Если метод public void metod(string s) — сделать статическим, тут же возникает проблема к обращению элементам окна textBox1, так как в статическом методе по определению нельзя использовать this. А кроме как обращения через this, к элементам окна (editBox1) начальной формы — незнаю. Замкнутый круг. (((((

Возможно есть какая либо переменная, на которую ссылается построенная при запуске программы форма.

А как насчет механизма событий? Применимы ли они в данном случае? Если можно — напишите код — буду очень благодарен!

  • Предложено в качестве ответа NIKkakNIK 14 марта 2011 г. 8:50
  • Помечено в качестве ответа Abolmasov Dmitry 14 марта 2011 г. 12:54

Экземпляр Form1 создается при запуске, если это прописано в функции Main, которая вызывается при запуске всей программы.

Предположим экземпляр действительно создается. Тогда Вам надо передавать указатель на этот объект в конструктор класса Klass1 и сохранять этот указатель внутри нового объекта. После чего можно вызывать все методы этого объекта.

Указатели на методы классов в C++

Решил написать статью об указателях на методы классов. Недавно мне пришлось столкнуться с тем, как они работают изнутри, когда писал некоторые вещи ориентированные под компилятор. Эти указатели работают не совсем как обычные указатели, не имеют возможности быть приведенными в void, и часто имеют размер больше 8 байт. Информации на эту тему в интернете я нашел относительно немного, потому решил разобраться сам.

Особенно пугают такие страшилки, которые мало что объясняют о том как происходит на самом деле и почему, а лишь пытаются приучить программиста слепо следовать требованиям.

Давайте разберемся что и почему происходит.
Все манипуляции будут произведены для архитектуры x86-64.

Взглянем на код.

Размер указателя на метод больше 8 байт. В некоторых компиляторах это не так, например компилятор Microsoft ужимает до 8 байт указатель на метод в некоторых случаях. В последних версиях компиляторов clang и gcc для Linux принимал размер 16 байт.

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

Посмотрим такой код на C++. Это базовый пример вызова метода из указателя на метод.

Скомпилировав код такой командой:

Получаем вывод LLVM IR:

LLVM IR является промежуточным представлением между машинным кодом и C++ в компиляторе Clang. Он позволяет компилятору производить оптимизации не зависящие от конкретной архитектуры процессора, а нам он дает понять что происходит на тех или иных стадиях компиляции, и является более читаемым чем язык ассемблера.

Подробнее про LLVM IR можно узнать в Википедии, официальном сайте LLVM и Clang.

  • Взглянув на первую строчку, видно что указатель на метод является структурой ` `, а не обычным указателем. Эта структура содержит два i64 элемента, которые могут уместить в себя 2 обычных указателя. Видно почему мы не можем приводить указатели на методы в обычные. Мы не можем без потерь преобразовать 16 байт в 8 байт в общем случае.
  • В блоке `entry`, начинающимся с 5 строки, видно что происходит корректирование указателя `this`. Это значит, что компилятор прибавляет к указателю на `this` значение второго элемента этой структуры, и позже в блоке `memptr.end` передает его в вызов метода.
  • Нечто странное происходит происходит в блоке `entry` на 14 строке с первым элементом структуры. Компилятор вычисляет выражение аналогичное следующему: `bool isvirtual = val & 1`. Компилятор считает указатель на метод виртуальным, если число в нем нечетное, в противном случае невиртуальным.
  • Если указатель на метод указывает на невиртуальный метод, то значение первого элемента считается обычным указателем на функцию, который позже вызывается. Эти предположения происходят в блоке `memptr.nonvirtual`.
  • Если указатель на метод указывает на виртуальный метод, то тут сложнее. Вначале вычитается единица из первого элемента структуры, и вычисленное значение является отступом для виртуальной таблицы, указатель на которую берется из значения указателя на `this`. Это происходит в блоке `memptr.virtual`.
  • Информацию является ли он виртуальным
  • Указатель на адрес метода (если не виртуальный)
  • Смещение в vtable (если виртуальный)
  • корректирование `this`

Метод класса имеет невидимый первый параметр — указатель на `this`, который передается компилятором при вызове метода. Остальные аргументы передаются после в том же порядке, что и были.

Если бы мы писали этот код на C++, то он выглядел бы примерно так:

Чтобы разобраться с значением корректирования, рассмотрим следующий пример:

Как мы видим, тут представлены примеры указателей на методы и аналогичные функции, которые принимают указатель на класс как указатель на `this`. Однако компилятор не может преобразовать указатели a1 и b1 в связи с тем, что мы не можем бесплатно преобразовывать указатели дочернего типа в указатели родительского типа. Компилятору необходимо запомнить отступ (значение корректирования) внутри дочернего класса для родительского класса и сохранить его где-то.

Посмотрим такой код:

Скомпилируем код командой:

Видно, что указатель на метод указывает на одну и ту же функцию. Однако значение корректирования разное из-за того что класс B расположен по сути внутри класса C.
Компилятору C++ необходимо знать отступ от базового класса для того чтобы передать `this` в метод класса.

Что плохого в этой реализации:

  • Размер указателя относительно большой, даже если корректирование отсутствует каким либо образом в gcc и clang
  • Каждый раз идет проверка виртуальности метода, даже если мы знаем что он не виртуальный
  • Использовать статический метод, принимающий экземпляр класса
  • Забыть про существование указателей на методы, и решить проблему как-то иначе в прочих случаях
  • В интернете есть советы использовать std::bind, std::function и подобные библиотечные функции. Проверив их поведение, я не обнаружил существования каких либо оптимизаций для указателей на методы.
  • У меня нет технической возможности проверить что происходит в компиляторах Microsoft, поэтому не особо про них рассказал. Однако протестировав онлайн компиляторы, я заметил что MSVC умеет анализировать структуру классов и удалять поле значения корректирования, если оно не требуется.

В данном примере компилятор не будет проверять метод на виртуальность, и сразу вызовет его как невиртуальный. Это лишь пример необычной оптимизации, не стоит использовать его в реальности.

Также я написал маленькую программку, что выводит данные об указателях на методы и помогает понять их внутренности, пока писал эту статью. Работает в clang и gcc под Linux. Код выложен тут.

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

Похожие статьи