Войти
ФлеймФорумПрограммирование

Динамические имена функций в Си. (3 стр)

Страницы: 1 2 3
#30
(Правка: 12:33) 12:31, 9 июля 2019

Aroch
> потому что указатель на метод это всего лишь смещение относительно адреса
> самого объекта, теоретически ты можешь грязным хаком получить адрес указателя
> на функцию конкретного объекта, но на кой оно надо в обычных программах?

Тут в том то и дело, что в настоящих программах нужен не функционал плюснутого указателя на метод, а функционал указателя на функцию, но с объектом в качестве первого параметра.

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

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

А нужны делегаты когда ты просто хочешь вызвать заранее известный метод заранее известного класса заранее известного объекта. Никакие довычисления в рантайме виртуальных функций и смещений при множественном наследовании там вообще не нужны. Вот просто не нужны и всё - наоборот оно только начинает вставлять большие палки в большие колёса - у указателей на методы множество ограничений и стопоров предохраняющих от неправильного использования в связи с таким сложным поведением, что чтобы нормально ими воспользоваться в std::function для реализации делегатов обязательно нужны обёртки в виде шаблонных распорок - просто для того чтобы забороть избыточную сложность этой языковой конструкции.


#31
12:42, 9 июля 2019

=A=L=X=
плюсы дают тебе способ не платить лишнего, когда тебе не нужно это лишние. Нет смысла выдумывать какой-то специальный указатель если тебе нужен к примеру только указатель на статичную или глобальную функцию.
>Т.е. когда мы вызывает метод через указатель в плюсах там могут начаться внутренние
> анализы - а не виртуальный ли это метод, а не виртуально ли отнаследован класс и всё
> это связано с довычислениями в рантайме.
ну если только компилятор совсем тупой, большую часть этих проверок можно провести на стадии компиляции.

#32
13:05, 9 июля 2019

Aroch
> ну если только компилятор совсем тупой, большую часть этих проверок можно
> провести на стадии компиляции.

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

Когда мы применяем (obj->*method)(...), то компилятор скорее всего не знает точный класс obj - то ли это MyClass, то ли его наследник, а значит в случае виртуального метода он начнёт динамически искать через vtbl местонахождение метода. С виртуальным наследованием вообще долго объяснять, но похожая ситуация - нужно еще знать как поправить указатель на obj до передачи в метод - ибо есть подводные камни. И эти вещи зашиваются в указатель на метод.

В случае же func_ptr( obj, ... ) ничего не ищется изначально - просто в функцию передаются параметры, если она была в оригинальном объекте виртуальной, то она тут уже "развиртуалена".
Т.е. такой вот гипотетический код:

    #include "iostream"
    struct X
    {
      virtual void f() { std::cout << "X::f()\n"; };
    };
    struct Y: public X
    {
      void f() override { std::cout << "Y::f()\n";  };
    };
     
    int main()
    {
      typedef void (X::*pointer)();
      pointer somePointer = &X::f;
      X *x = new Y;
      (x ->* somePointer)();
    };
Выведет Y::f() - https://ideone.com/GhT60C

А вот если бы мы взяли указатель на метод X::f как на настоящую функцию вывелось бы X::f() потому что вызов был бы эквивалентен x->X::f() - и именно такое поведение на самом деле и нужно в большинстве практических случаев. Практические случаи же где плюснутых указателей на функции - это его имитация. От и до.

#33
13:43, 9 июля 2019

=A=L=X=
не понимаю зачем ты мне это говоришь, это и так известно. И конкретно твой пример компилятор как раз таки может оптимизировать и вычислить нужно смещение сразу. Многие компиляторы располагают таблицу методов базового класса сразу и потом уже только добавляют новые методы от наследуемых классов, то есть этот финт опять же помогает им сразу знать нужное смещение. Поэтому не важно виртуальный у тебя метод или нет, смещение до этого места в таблице будет располагаться в одном месте, то есть сперва сделаем чтение из таблицы если это виртуальный метод и потом уже вызовем сам метод. Но это стандартная плата за виртуальность, без относительно темы разговора.

#34
(Правка: 13:47) 13:47, 9 июля 2019

Aroch
> Но это стандартная плата за виртуальность, без относительно темы разговора.

Плюс еще плата за наследование и его возможную виртуальность - в результате sizeof() указателей на методы плавает от 8 байт до 16 байт на 32-битной платформе и естественно они просто не могут поэтому быть совместимыми ни сами друг с другом ни с указателями на функции - это вообще излишне сложная и ненужная сущность в языке, придуманная Страуструпом просто потому что он подумал что так круче. А оно так не оказалось, так что живём с тем что есть.

#35
13:58, 9 июля 2019

=A=L=X=
Там где это критично от виртуальности отказываются, либо финальный тип объекта в месте вызова известен. Во всяких же гуях потерять каплю производительности на виртуальных методах совершенно не страшно, по сравнению со всей остальной работой это действительно капля.

#36
(Правка: 17:28) 17:27, 9 июля 2019

=A=L=X=
> Указатели же на методы плюсов ведут себя иначе - это очень комплексные структуры
Некоторые компиляторы сделали это нормально: указатель на метод = указатель на функцию.
Очень плохо, что в стандарте не зафиксировали, что все указатели на методы должны свободно конвертироваться туда и обратно в (void *).

#37
(Правка: 17:39) 17:33, 9 июля 2019

}:+()___ [Smile]
> Некоторые компиляторы сделали это нормально: указатель на метод = указатель на
> функцию.

Это чисто физически возможно только для очень простых случаев - невиртуальные методы без множественного (особенно виртуального) наследования.
Именно поэтому у них крайне плавающий размер в зависимости от всех этих факторов и никакая абсолютно степень совместимости при конвертациях во что бы то ни было - это просто невозможно сделать физически в обобщённом случае. Ни о каком void * никакой речи тут и быть не может - сразу же потеря данных просто на битовом уровне либо sizeof(void*) в 16-18 байт на 32-битной платформе для покрытия всех возможных вариантов того чем может быть указатель на метод.

Совсем другой подход мог бы быть если бы у любого класса операцией MyClass::&method_name можно было бы взять указатель на функцию с первым-параметром типа MyClass * гарантированного размера указателя на обычную свободную функцию и при этом легко конвертировать его в точно такую же сигнатуру только с void * в качестве первого параметра - тогда все размеры были бы всегда фиксированы и совпадали бы с размером любого указателя на функцию. И этого за глаза хватало бы для безобразно простой реализации делегатов в std::function.

#38
17:36, 9 июля 2019

для полноты картины сброшу классическую уже статью
https://www.codeproject.com/Articles/7150/Member-Function-Pointer… test-Possible

#39
18:44, 9 июля 2019

=A=L=X=
> Это чисто физически возможно только для очень простых случаев - невиртуальные методы без множественного (особенно виртуального) наследования.
Компилятор всегда может сгенерировать необходимый трамплин при взятии адреса виртуальной функции.
Более того, некоторые компиляторы (судя по godbolt, это MSVC, надо же, не ожидал) это делают.

#40
(Правка: 19:02) 18:58, 9 июля 2019

}:+()___ [Smile]
> трамплин

А, трамплин да, если все их заворачивать всегда в трамплины то вроде бы можно гарантировать правильное поведение.
Но опять таки зашивают правки на vtbl и class offset в сами трамплины и совместимости всё равно никакой, а теоретически можно было бы обойтись вообще без трамплинов напрямую дёргая методы как функции приняв в стандарт их эквивалетность с точностью до первого параметра-указателя на класс. Плюс еще при таком раскладе легко реализуются такие плюшки как свободные функции с первым параметром - указателем на класс/структуру которые вызываются так как будто бы они методы класса. Ну потому что опять таки на внутреннем уровне разницы нет. Но главное это конечно способность сравнивать, заносить в таблицы и, следовательно, сериализовать делегаты довольно простым образом и т.п. и т.д.
P.S.
Ах да, кстати, вспомнил что сам делал вручную трамплины на шаблонах как раз для гарантий переносимости где то в рамках тестов - работало. Т.е. трамплин генерировался шаблоном в явном виде и был именно свободной функцией которая первому параметру-указателю-на-класс дёргала метод с нужной сигнатурой. Как раз для делегатов где то такое тут на форуме придумывали.

#41
19:30, 9 июля 2019

=A=L=X=
> приняв в стандарт их эквивалетность с точностью до первого параметра-указателя на класс
Будут проблемы с виртуальными функциями.

Но, вообще, да, стоило бы убрать все эти сложности с указателями на виртуальные функции.
Вызов Base::fun() — это вызов версии функции из базового класса, так почему &Base::fun — это указатель на непонятно что?

> Т.е. трамплин генерировался шаблоном в явном виде и был именно свободной функцией
Так это же стандартная практика. У меня в каждом проекте с callback-ами есть соответствующая обертка.

#42
(Правка: 20:14) 20:12, 9 июля 2019

}:+()___ [Smile]
> Будут проблемы с виртуальными функциями.

А они и не нужны, делегат конструируется в момент создания когда и класс и объект известны и никакая виртуальность не нужна - просто берется указатель на конкретный метод конкретного класса чей объект мы и обрабатываем.
А все эти сложности с обработкой виртуальных функций через указатели на методы - они не нужны на практике. Я ни одного паттерна с ними не видел, ни одной идиомы или полезного трюка - сами виртуальные функции вполне достаточны в ООП чтобы решать дела для которых они сделаны - делегатам эта тонкость не нужна от слова вообще. Делегатам нужно только то, чтобы через них отложенно можно было вызвать где то там в клиентском коде метод объекта, причём так чтобы в месте вызова вообще не было нужно знать класс объекта - только лишь сигнатуру функции без первого незримого данного указателя-на-объект. Но в момент конструирования делегата мы совершенно точно знаем и объект и класс - виртуальность тут - лишнее.

#43
20:48, 9 июля 2019

=A=L=X=
> А они и не нужны
Это я по поводу полной эквивалентности функций-членов и глобальных функций.
Виртуальные в это схему не укладываются.

> А все эти сложности с обработкой виртуальных функций через указатели на методы - они не нужны на практике.
Это да, непонятно зачем придуманная фича, которая никому не нужна, но за собой тянет кучу геморроя.

<holywar> Как и виртуальное наследование, как и эксепшены. </holywar>

Страницы: 1 2 3
ФлеймФорумПрограммирование