Приведение указателей на функции-члены и вызов
От: niralex  
Дата: 02.12.19 07:27
Оценка:
Доброго! Можно ли привести указатель на функцию-член класса к другому независимому классу и потом вызвать функцию?
Например, код ниже работает, но является ли он безопасным/переносимым, если предполагать что класс Concret может быть любым, включая различные виды наследования(множественное, виртуальное и т.д.)?

// С++17
#include <iostream>
using namespace std;

class Common; // без определения
class Concret // может быть наследование
{
    public:
        void Test(){std::cout << "ok" << endl; }
};

using ConcretMemberPointer = void (Concret::*) ();
using MemberPointer = void (Common::*) ();

int main()
{
    Concret *ob = new Concret;
    ConcretMemberPointer cmp = &Concret::Test;
    (ob->*cmp)(); // ok
    MemberPointer mp = reinterpret_cast<MemberPointer>(cmp);
    Common *com = reinterpret_cast<Common*>(ob);
    (com->*mp)(); // ok, но безопасно ли???
    return 0;
}
Re: Приведение указателей на функции-члены и вызов
От: reversecode google
Дата: 02.12.19 08:29
Оценка: +2
reinterpret_cast уже ничего не гарантируется
Re: Приведение указателей на функции-члены и вызов
От: Шахтер Интернет  
Дата: 02.12.19 12:47
Оценка:
Здравствуйте, niralex, Вы писали:

N>Доброго! Можно ли привести указатель на функцию-член класса к другому независимому классу и потом вызвать функцию?


Нет. Классы должны быть связаны наследованием, для приведения типа нужно использовать static_cast.

N>Например, код ниже работает, но является ли он безопасным/переносимым, если предполагать что класс Concret может быть любым, включая различные виды наследования(множественное, виртуальное и т.д.)?


UB.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: Приведение указателей на функции-члены и вызов
От: B0FEE664  
Дата: 02.12.19 13:09
Оценка: +2 :)
Здравствуйте, niralex, Вы писали:

Можно ли привести указатель на функцию-член класса к другому независимому классу и потом вызвать функцию?
Нет.

А вам зачем?
И каждый день — без права на ошибку...
Re[2]: Приведение указателей на функции-члены и вызов
От: niralex  
Дата: 02.12.19 20:39
Оценка:
Здравствуйте, Шахтер, Вы писали:

Ш>Нет. Классы должны быть связаны наследованием, для приведения типа нужно использовать static_cast.


В одной уважаемой статье
Автор(ы): Don Clugston
Дата: 27.07.2005
В данной статье предоставлен исчерпывающий материал по указателям на функции-члены, а также приведена реализация делегатов, которые занимают всего две операции на ассемблере.
на RSDN написано со ссылкой на стандарт:

В соответствии со стандартом (секция 5.2.10/9), разрешено использование reinterpret_cast для хранения указателя на член одного класса внутри указателя на член независимого класса.

Там же есть и ответ на мой вопрос:

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

Но это было в 2004 году. Вот я и подумал, может что-то поменялось в лучшую сторону за 16 лет. Видимо, нет
Re[2]: Приведение указателей на функции-члены и вызов
От: niralex  
Дата: 02.12.19 21:01
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>А вам зачем?


Например, сделать и использовать массив указателей на функции-члены независимых классов. В реальности задача сложнее.
Re[3]: Приведение указателей на функции-члены и вызов
От: PM  
Дата: 03.12.19 05:59
Оценка: +1
Здравствуйте, niralex, Вы писали:

N>Например, сделать и использовать массив указателей на функции-члены независимых классов. В реальности задача сложнее.


Для этого есть https://en.cppreference.com/w/cpp/utility/functional/function
Re[3]: Приведение указателей на функции-члены и вызов
От: B0FEE664  
Дата: 03.12.19 12:25
Оценка:
Здравствуйте, niralex, Вы писали:

BFE>>А вам зачем?

N>Например, сделать и использовать массив указателей на функции-члены независимых классов. В реальности задача сложнее.

Задача по составлению массива функций для вызова членов независимых классов вполне решаема с помощью различных подходов, но нужны подробности:
— эти массивы функций константные?
— почему вам не подходит технология signal-slot?
— синтакс вызова фиксирован ? (т.е. это call-back'и или можно использовать std::function?)
И каждый день — без права на ошибку...
Re[3]: Приведение указателей на функции-члены и вызов
От: Шахтер Интернет  
Дата: 03.12.19 13:52
Оценка:
Здравствуйте, niralex, Вы писали:

N>Здравствуйте, Шахтер, Вы писали:


Ш>>Нет. Классы должны быть связаны наследованием, для приведения типа нужно использовать static_cast.


N>В одной уважаемой статье
Автор(ы): Don Clugston
Дата: 27.07.2005
В данной статье предоставлен исчерпывающий материал по указателям на функции-члены, а также приведена реализация делегатов, которые занимают всего две операции на ассемблере.
на RSDN написано со ссылкой на стандарт:

N>

N>В соответствии со стандартом (секция 5.2.10/9), разрешено использование reinterpret_cast для хранения указателя на член одного класса внутри указателя на член независимого класса.

N>Там же есть и ответ на мой вопрос:
N>

N>Результат вызова функции в приведенном указателе не определен. Единственное, что можно с ним сделать — это привести его назад к классу, от которого он произошел. (...) в этой области Стандарт имеет мало сходства с реальными компиляторами.

N>Но это было в 2004 году. Вот я и подумал, может что-то поменялось в лучшую сторону за 16 лет. Видимо, нет

За 16 лет 2+2=4 не изменилось.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: Приведение указателей на функции-члены и вызов
От: Pzz Россия https://github.com/alexpevzner
Дата: 03.12.19 19:15
Оценка: 1 (1)
Здравствуйте, niralex, Вы писали:

N>Доброго! Можно ли привести указатель на функцию-член класса к другому независимому классу и потом вызвать функцию?


Указатели на метод класса — они не совсем указатели. Это целая структура, хоть и небольшая, в ней, кроме, собственно, адреса функции, содержится информация, на сколько корректировать this при вызове, и как добраться до адреса функции, в случае, если метод виртуален.

Если указатель на метод привести к указателю на метод какого-то другого класса, то эта дополнительная информация станет совершенно нерелевантна. Так что может оно и сработает, если повезет, а может и не сработает, и заранее никто не скажет.
Re[4]: Приведение указателей на функции-члены и вызов
От: niralex  
Дата: 03.12.19 22:57
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Здравствуйте, niralex, Вы писали:


BFE>>>А вам зачем?

N>>Например, сделать и использовать массив указателей на функции-члены независимых классов. В реальности задача сложнее.

BFE>Задача по составлению массива функций для вызова членов независимых классов вполне решаема с помощью различных подходов, но нужны подробности:

BFE>- эти массивы функций константные?
BFE>- почему вам не подходит технология signal-slot?
BFE>- синтакс вызова фиксирован ? (т.е. это call-back'и или можно использовать std::function?)

На самом деле задача сложнее. Нужно реализовать систему связывания вызываемых сущностей(условно назовем "выходы") с функциями-членами независимых классов(назовем условно "входы").
Например, в одном классе есть член типа std::function<void()> а в другом функция-член void f(). Нужно их связать. Все было бы до смешного просто, если бы не ряд требований:
1. Классы должны быть обобщены и иметь унифицированный интерфейс для связывания, например, такой:
class Component
{
   public:
      
    // Функция во время выполнения проверяет сигнатуры входа и выхода и если они совпадают, то связывает выход с входом.
    // outputIndex - номер выхода
    // Component - объект, содержащий вход
    // inputIndex - номер входа 
    virtual bool Bind(size_t outputIndex, Component *in, size_t inputIndex)=0;
    
    // Функции, предоставляющие информацию о входах и выходах компонента 
    //...
                    
};

2. Компоненты(связываемые объекты) могут находится в разных dll, созданных разными компиляторами и которые могут динамически загружаться и выгружаться.
3. Критичны скорость связывания, скорость вызовов, размер выходов(std::function)

Исходя из описанной задачи, отвечу на вопросы:

— эти массивы функций константные?

да, массивы используются для хранения входов и выходов. Размер массивов известен на этапе компиляции, но инициализировать придется все же во время выполнения.

— почему вам не подходит технология signal-slot?

не знаком с этой технологией, подозреваю что она решает аналогичную задачу, уверен, что сделаю лучше

— синтакс вызова фиксирован ? (т.е. это call-back'и или можно использовать std::function?)

не call-back. std::function можно было бы использовать, но как их обобщить с разными сигнатурами и как быть с dll, интерфейс которых должен быть в C-стиле(без классов)

Решая эту задачу я столкнулся с вопросом, который в теме, но уже понял ответ — UB
Извиняюсь за "кривую" терминологию и описание — я не профи, программирую в свободное время "для души"
Re[5]: Приведение указателей на функции-члены и вызов
От: Chorkov Россия  
Дата: 04.12.19 10:38
Оценка: 2 (2)
Здравствуйте, niralex, Вы писали:


N>1. Классы должны быть обобщены и иметь унифицированный интерфейс для связывания, например, такой:

N>
N>class Component
N>{
N>   public:
      
N>    // Функция во время выполнения проверяет сигнатуры входа и выхода и если они совпадают, то связывает выход с входом.
N>    // outputIndex - номер выхода
N>    // Component - объект, содержащий вход
N>    // inputIndex - номер входа 
N>    virtual bool Bind(size_t outputIndex, Component *in, size_t inputIndex)=0;
    
N>    // Функции, предоставляющие информацию о входах и выходах компонента 
N>    //...
                    
N>};
N>

N>2. Компоненты(связываемые объекты) могут находится в разных dll, созданных разными компиляторами и которые могут динамически загружаться и выгружаться.

На этом можно остановиться. П. 1 и 2 несовместимы.

Это и есть ровным счетом signal/slot, даже чуть-чуть меньше.
Единственное что, ни в одной известной реализации не заворачиваются с "cross-compiler binary compatibility". Возможно, потому что Component с виртуальными функциями уже не переносим, поскольку нет ABI для С++.

Популярные реализации:
1) Qt: https://doc.qt.io/qt-5/signalsandslots.html
2) boost: https://www.boost.org/doc/libs/1_66_0/doc/html/signals2.html

Посмотрите как там что сделано...

Если все-таки решите закатывать солнце своими руками, просто добавьте в Component функцию:
class Component
{
protected:
virtual void InvokeSlot( size_t inputIndex, void * argumentsTuple );
...
};
внутри которой и распаковывайте указатели на аргументы из argumentsTuple.
Re[5]: Приведение указателей на функции-члены и вызов
От: Videoman Россия https://hts.tv/
Дата: 04.12.19 12:43
Оценка: +1
Здравствуйте, niralex:

Ну так std::function шаблон, но он зависит только от сигнатуры вызова, так что связывайте что хотите когда хотите, если я правильно понял что нужно:
struct Proxy
{
    std::function<int (int param)> handler;

    int Call(int param) { return handler(param); }
};

struct Stub
{
    int Callee(int param) { return param; }
};

Допустим есть два объекта: Proxy — через него вызываем, Stub — что вызываем.
// ...Дальше где-то создаем эти объекты:
Stub stub;
Proxy proxy;
// ...дальше где хотим связываем как хотим
proxy.handler = [&stub](int param) { return stub.Callee(param); }; // Связывает тут что хотим с чем хотим
// ...дальше вызываем
assert(proxy.Call(123) == 123);
// или так
assert(proxy.handler(123) == 123);

В лямбду можно запихнуть что угодно, в том числе умный хендлер, который при разрушении будет освобождать ресурсы, например ту же dll.
Отредактировано 04.12.2019 12:46 Videoman . Предыдущая версия . Еще …
Отредактировано 04.12.2019 12:46 Videoman . Предыдущая версия .
Re[5]: Приведение указателей на функции-члены и вызов
От: B0FEE664  
Дата: 04.12.19 14:33
Оценка:
Здравствуйте, niralex, Вы писали:

  описание
N>На самом деле задача сложнее. Нужно реализовать систему связывания вызываемых сущностей(условно назовем "выходы") с функциями-членами независимых классов(назовем условно "входы").
N>Например, в одном классе есть член типа std::function<void()> а в другом функция-член void f(). Нужно их связать. Все было бы до смешного просто, если бы не ряд требований:
N>1. Классы должны быть обобщены и иметь унифицированный интерфейс для связывания, например, такой:
N>
N>class Component
N>{
N>   public:
      
N>    // Функция во время выполнения проверяет сигнатуры входа и выхода и если они совпадают, то связывает выход с входом.
N>    // outputIndex - номер выхода
N>    // Component - объект, содержащий вход
N>    // inputIndex - номер входа 
N>    virtual bool Bind(size_t outputIndex, Component *in, size_t inputIndex)=0;
    
N>    // Функции, предоставляющие информацию о входах и выходах компонента 
N>    //...
                    
N>};
N>

N>2. Компоненты(связываемые объекты) могут находится в разных dll, созданных разными компиляторами и которые могут динамически загружаться и выгружаться.
N>3. Критичны скорость связывания, скорость вызовов, размер выходов(std::function)


Описание почти полностью совпадает с OLE — ActiveX фреймворком компании Microsoft.
Вы количество и типы параметров функций, которые находятся в dll'ях знаете заранее или нет?
И каждый день — без права на ошибку...
Re[6]: Приведение указателей на функции-члены и вызов
От: niralex  
Дата: 04.12.19 23:00
Оценка:
Здравствуйте, Chorkov, Вы писали:

C>На этом можно остановиться. П. 1 и 2 несовместимы.

C>Это и есть ровным счетом signal/slot, даже чуть-чуть меньше.
C>Единственное что, ни в одной известной реализации не заворачиваются с "cross-compiler binary compatibility". Возможно, потому что Component с виртуальными функциями уже не переносим, поскольку нет ABI для С++.
Можно использовать свой бинарный формат для аргументов и возвращаемых значений.

C>Популярные реализации:

C>1) Qt: https://doc.qt.io/qt-5/signalsandslots.html
C>2) boost: https://www.boost.org/doc/libs/1_66_0/doc/html/signals2.html

C>Посмотрите как там что сделано...

Хочу свое
Re[6]: Приведение указателей на функции-члены и вызов
От: niralex  
Дата: 04.12.19 23:16
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Описание почти полностью совпадает с OLE — ActiveX фреймворком компании Microsoft.

Значит я на правильном пути!

BFE>Вы количество и типы параметров функций, которые находятся в dll'ях знаете заранее или нет?


Интерфейс библиотек позволяет получить информацию о количестве и типах аргументов и возвращаемого значения.
А чтобы была бинарная совместимость, используюется "свой" бинарный формат. Он реализуется с помощью простых кодеров/декодеров для стандартных типов и контейнеров, например:
  Скрытый текст
struct Int32
{
    using Type = int32_t; // стандартный базовый тип
    template<typename T> static void Encoder(Buffer *buffer, Size &pos, T value)
    {
        impl::EncoderPOD<Type>(buffer, pos, value);
    }
    static void Decoder(const Buffer *buffer, Size &pos, Type &value)
    {
        impl::DecoderPOD<Type>(buffer, pos, value);
    }
    static constexpr auto Signature() { /*...*/ }
    static constexpr const char *Name() { return "NInt32"; }
};
// Аналогично написаны кодеки для всех стандартных типов (почти для всех :) ) 
// ...

// Кодирование пакета значений в буфер
template<typename ...TEncoders>
Buffer *Encode(Buffer *buffer, typename TEncoders::Type... vals) { /*...*/ }

// Декодирование произвольного количества значений из буфера
template<typename ...TDecoders>
const Buffer *Decode(const Buffer *buffer, typename TDecoders::Type&... vals)  { /*...*/ }

Для функций-членов используются специальные оболочки(входы). Параметры предварительно кодируются для бинарной совместимости.
Затем вход их декодирует и передает соответствующей функции-члену. При связывании выхода со входом сравниваются сигнатуры, которые должны совпадать. В упрощенном входы выглядят так:
  Скрытый текст
class Input
{
    public:
        // Активизация входа(вызов функции, связанной с входом) 
        // buffer - буфер с закодированными параметрами, куда также записывается возвращаемое значение
        virtual void Call(Buffer *buffer)const=0;
        virtual const Signature *GetSignature()const=0; // получение сигнатуры входа
};
template<typename TComponentType, typename TRetCodec, typename ...TArgsCodec>
class ComponentInput : public Input
{
    private:
        using TRet = typename TRetCodec::Type; // тип возвращаемого значения
        // тип указателя на функцию-член
        using TMemberFunction = TRet (TComponentType::*)(typename TArgsCodec::Type...); 
        TMemberFunction _funcMember; // указатель на функцию член-компонента

    public:
        ComponentInput(TMemberFunction funcMember)
              : _funcMember(funcMember) {}
              
        void Call(Buffer *buffer)const
        {
            std::tuple<typename TArgsCodec::Type...> t_args;
            std::apply([buffer, &_func=_func](auto ...args)
            {
                // Декодирование указателя на компонент и аргументов args из буфера buffer
                uint64_t com;
                Decode<Uint64, TArgsCodec...>(buffer, com, args...);
                if constexpr(std::is_void<TRet>::value) // если функция возвращает void
                    (((TComponentType*)com)->*_funcMember)(args...); // вызов функции входа без обработки возвращаемого значения
                // вызов функции входа и кодирование возвращаемого значения в буфер (возврат результата через буфер параметров)
                else Encode<TRetCodec>(buffer, (((TComponentType*)com)->*_funcMember)(args...));
            }, t_args);
        }
        const Signature *GetSignature()const {/*...*/}
};

Вместо std::function используется свой класс, параметризируемый кодеками для аргументов. Упрощенный набросок:
  Скрытый текст
class Output
{
    public:
        virtual void Bind(Component *com, Input *input)=0;
};
template<typename TRetCodec, typename ...TArgsCodec>
class ComponentOutput : public Output
{
    private:
        using TRet = typename TRetCodec::Type;
        Component *_com;
        Input *_input;

    public:
        void Bind(Component *com, Input *input) // Связывание
        {
            // Проверка сигнатур
            // ...
            _com = com;
            _input = input;
        }
        TRet Call(typename TArgsCodec::Type... args)
        {
            Buffer buffer[PARAMS_BUF_SIZE]; // буфер для параметров
            // кодирование адреса компонента и параметров, вызов входа
            _input->Call(Encode<UInt64, TArgsCodec...>(buffer, _com, args...)); 
            return DecodeValue<TRetCodec>(buffer);  // декодирование возвращаемого значения
        }
};

При описании компонента определяются все входы и выходы с указанием типов кодеков для возвращаемого значения и параметров:
  Скрытый текст
class MyComponent : public Component
{
    // ...
    constexpr static auto _tinputs{make_tuple(
        ComponentInput<Void, Void>(&MyComponent::f1),
        ComponentInput<Uint32, Int8>(&MyComponent::f2)
        // ...
    )};
    // ...
    ComponentOutput<Void, Void> func1;
    ComponentOutput<Uint32, Int8> func2;
    // ...
};

Для обобщения указатели на входы и выходы заносятся в array.
В итоге можно связывать компоненты таким образом:
Component *com1(ComponentFactory->Make("MyComponent"));
Component *com2(ComponentFactory->Make("OtherComponent"));
com1->Bind(0, com2, 1); // сигнатуры нулевого выхода com1 и первого входа com2 должны совпадать

Это очень упрощенный набросок. В реальности уже было реализовано связывание для компонентов из разных dll.
В настоящий момент делаю рефакторинг, и... задаю вопросы Спасибо всем за помощь!
Re[7]: Приведение указателей на функции-члены и вызов
От: Chorkov Россия  
Дата: 05.12.19 07:55
Оценка:
Здравствуйте, niralex, Вы писали:

N>Можно использовать свой бинарный формат для аргументов и возвращаемых значений.


Для аргументов — без проблем. а для базового класса (Component)?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.