Я написал класс для хранения указателя функции или указателя на функцию-член (не одновременно). Когда я сохраняю указатель на функцию-член, я также сохраняю указатель на объект (получатель).
Проблема в том, что я заранее не знаю ни типа объекта, ни сигнатуры функции, поэтому я использую типовое имя шаблона. Для аргументов я использую вариационные шаблоны.
У меня есть код, похожий на этот:
template <typename... Args>
class A
{
public:
template <typename Object, typename Ret>
A (Object *receiver, Ret (Object::*mem)(Args...)); // store the member function pointer
template <typename Ret>
A (Ret (*function)(Args...)); // store the global function pointer
void operator()(Args... args) const; // to call the function pointer
// ... more public stuff ...
private:
class UndefinedClass;
typedef void (Undefined::*MemFunPtrType)(Args...)
union
{
struct
{
MemFunPtrType memFunPtr; // the stored member function
void *receiverPtr; // the receiver object
void (*call)(void *, MemFunPtrType, Args...);
} member;
void (*funPtr)(Args...); // the stored global function
};
enum class Type {Function, Member} type;
};
Поскольку нужна только одна, глобальная функция или функция-член, я помещаю все внутри union
,
В конструкторе я бросил функцию-член mem
к void (Undefined::*)(Args...)
и сохранить его. Я взял этот трюк из реализации std :: function.
Используя лямбду без захвата, я снова приведу к исходному типу, как объекту, так и функции, и вызываю их:
typedef Ret (Object::*OriginalMemberType)(Args...);
// ...
member.call = [] (void *receiver, MemFunPtrType memFun, Args... args)
{
(static_cast<Object*>(receiver)->*reinterpret_cast<OriginalMemberType>(memFun))(args...);
}
Я храню эту лямбду в call
указатель на функцию для вызова внутри operator()
, С предложением if-else я сравниваю данные типа и вызываю правильный указатель.
Я знаю, что это немного раздражает, но это работает. У меня много тестов, и все они проходят. Но я беспокоюсь о строгое наложение вещь. Я часто использую указатели, и я не уверен, что это приводит к неопределенному поведению.
Вопрос: Разрешено ли кастовать из Ret (Object::*)(Args...)
в void (UndefinedClass::*)(Args...)
? (Ret, объект а также Args являются аргументами шаблона)
Заметьте, я никогда не вызываю объект без приведения снова к исходному типу. Это только для хранения.
Вопрос:: Должен ли я скомпилировать с -fno-strict-aliasing
? Если я должен, и sice это класс шаблона, я должен поставить -fno-strict-aliasing
в каждом проекте, использующем этот класс? Может быть, я мог бы использовать массив char
длины sizeof(void (UndefinedClass::*)(Args...))
или что-то в этом роде.
Заметки:
Огромное спасибо. Я уточню любой аспект этого беспорядка, если потребуется 🙂
Пока вы не обращаетесь к адресу с указанным неверным типом указателя. Вы не нарушаете правила. так что это не проблема. Вы просто нарушаете правило, если обращаетесь к нему с помощью неверного перфорированного указателя на другой тип, который может рассматриваться как похожий, но не тот же. Возможно, вы уже использовали сокеты FreeBSD, они выдают в функцию bind () также строгое предупреждение псевдонимов, потому что ваше приведение sockadr_in к sockadr и внутри него приведено обратно к соответствующему типу реализации. В предупреждении говорится, что это может нарушить правило, но реализация этого не позволяет.
Как хорошо описано в этом вопросе: Сокеты Беркли, нарушающие правила наложения имен?
И если вы даже не уверены в том, на что вы пойдете, вы также можете сделать приведение к char *. Потому что (начиная с c99, и я полагаю, что это должно быть одинаково в стандартах c ++), char * разрешен для псевдонимов всего Так как вы находитесь в C ++, вы даже можете обрабатывать с помощью шаблонов приведение типов к типу char и обратно внутри функции. (Хорошо, приведение типа char * не очень хорошая идея, если ваши функции предназначены не только для внутреннего использования.)
Других решений пока нет …