Доброго! Можно ли привести указатель на функцию-член класса к другому независимому классу и потом вызвать функцию?
Например, код ниже работает, но является ли он безопасным/переносимым, если предполагать что класс 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: Приведение указателей на функции-члены и вызов
Здравствуйте, niralex, Вы писали:
N>Доброго! Можно ли привести указатель на функцию-член класса к другому независимому классу и потом вызвать функцию?
Нет. Классы должны быть связаны наследованием, для приведения типа нужно использовать static_cast.
N>Например, код ниже работает, но является ли он безопасным/переносимым, если предполагать что класс Concret может быть любым, включая различные виды наследования(множественное, виртуальное и т.д.)?
В соответствии со стандартом (секция 5.2.10/9), разрешено использование reinterpret_cast для хранения указателя на член одного класса внутри указателя на член независимого класса.
Там же есть и ответ на мой вопрос:
Результат вызова функции в приведенном указателе не определен. Единственное, что можно с ним сделать — это привести его назад к классу, от которого он произошел. (...) в этой области Стандарт имеет мало сходства с реальными компиляторами.
Но это было в 2004 году. Вот я и подумал, может что-то поменялось в лучшую сторону за 16 лет. Видимо, нет
Re[2]: Приведение указателей на функции-члены и вызов
Здравствуйте, niralex, Вы писали:
N>Например, сделать и использовать массив указателей на функции-члены независимых классов. В реальности задача сложнее.
Здравствуйте, niralex, Вы писали:
BFE>>А вам зачем? N>Например, сделать и использовать массив указателей на функции-члены независимых классов. В реальности задача сложнее.
Задача по составлению массива функций для вызова членов независимых классов вполне решаема с помощью различных подходов, но нужны подробности:
— эти массивы функций константные?
— почему вам не подходит технология signal-slot?
— синтакс вызова фиксирован ? (т.е. это call-back'и или можно использовать std::function?)
И каждый день — без права на ошибку...
Re[3]: Приведение указателей на функции-члены и вызов
Здравствуйте, niralex, Вы писали:
N>Здравствуйте, Шахтер, Вы писали:
Ш>>Нет. Классы должны быть связаны наследованием, для приведения типа нужно использовать static_cast.
N>В одной уважаемой статье
N>В соответствии со стандартом (секция 5.2.10/9), разрешено использование reinterpret_cast для хранения указателя на член одного класса внутри указателя на член независимого класса.
N>Там же есть и ответ на мой вопрос: N>
N>Результат вызова функции в приведенном указателе не определен. Единственное, что можно с ним сделать — это привести его назад к классу, от которого он произошел. (...) в этой области Стандарт имеет мало сходства с реальными компиляторами.
N>Но это было в 2004 году. Вот я и подумал, может что-то поменялось в лучшую сторону за 16 лет. Видимо, нет
Здравствуйте, niralex, Вы писали:
N>Доброго! Можно ли привести указатель на функцию-член класса к другому независимому классу и потом вызвать функцию?
Указатели на метод класса — они не совсем указатели. Это целая структура, хоть и небольшая, в ней, кроме, собственно, адреса функции, содержится информация, на сколько корректировать this при вызове, и как добраться до адреса функции, в случае, если метод виртуален.
Если указатель на метод привести к указателю на метод какого-то другого класса, то эта дополнительная информация станет совершенно нерелевантна. Так что может оно и сработает, если повезет, а может и не сработает, и заранее никто не скажет.
Re[4]: Приведение указателей на функции-члены и вызов
Здравствуйте, 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]: Приведение указателей на функции-члены и вызов
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 для С++.
Если все-таки решите закатывать солнце своими руками, просто добавьте в Component функцию:
class Component
{
protected:
virtual void InvokeSlot( size_t inputIndex, void * argumentsTuple );
...
};
внутри которой и распаковывайте указатели на аргументы из argumentsTuple.
Re[5]: Приведение указателей на функции-члены и вызов
Допустим есть два объекта: 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.
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]: Приведение указателей на функции-члены и вызов
Здравствуйте, 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]: Приведение указателей на функции-члены и вызов
Здравствуйте, 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 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]: Приведение указателей на функции-члены и вызов