Приведение из указателя на функцию-член к другому типу и обратно, проблема строгого наложения имен?

Я написал класс для хранения либо указателя функции, либо указателя на функцию-член (не одновременно). Когда я сохраняю указатель на функцию-член, я также сохраняю указатель на объект (получатель).

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

У меня есть код, похожий на этот:

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, Object и Args являются аргументами шаблона). Примечание. Я никогда не вызываю объект без повторного приведения к исходному типу. Это только для хранения.

Вопрос:: Должен ли я скомпилировать с -fno-strict-aliasing? Если я должен, и sice это класс шаблона, я должен поставить -fno-strict-aliasing в каждом проекте, использующем этот класс? Может быть, я мог бы использовать массив char длины sizeof(void (UndefinedClass::*)(Args...)) или что-то в этом роде.

Заметки:

  1. Я использую gcc 4.8.1 в Archlinux. Я использую C++11.
  2. Я не использовал std:: function здесь только для того, чтобы использовать заполнители с std:: bind (я не знаю, сколько требуется заполнителей). И это хорошая практика и обучение в конце концов. Если вы знаете, как это сделать с помощью std:: function, ответ очень приветствуется.
  3. На самом деле, я использую этот класс в std:: vector для вызова многих "обратных вызовов" с одной и той же сигнатурой (каркас типа сигнал / слот).

Огромное спасибо. Я уточню любой аспект этого беспорядка, если потребуется:)

1 ответ

Решение

Пока вы не обращаетесь к адресу с указанным неверным типом указателя. Вы не нарушаете правила. так что это не проблема. Вы просто нарушаете правило, если обращаетесь к нему с помощью неверного перфорированного указателя на другой тип, который может рассматриваться как похожий, но не тот же. Возможно, вы уже использовали сокеты FreeBSD, они выдают в функцию bind() также строгое предупреждение о псевдонимах, потому что ваше приведение sockadr_in к sockadr и внутри него приведено обратно к соответствующему типу реализации. В предупреждении говорится, что это может нарушить правило, но реализация этого не позволяет. Как хорошо описано в этом вопросе: сокеты Беркли, нарушающие правила наложения имен?

И если вы даже не уверены в том, на что вы пойдете, вы также можете выполнить приведение к char*. Потому что (начиная с c99, и я полагаю, что это должно быть одинаково в стандартах C++), char * разрешено для псевдонимов всего Так как вы находитесь в C++, вы даже можете обрабатывать с помощью шаблонов приведение типов к типу char и обратно внутри функции. (Хорошо, приведение типа char * не очень хорошая идея, если ваши функции предназначены не только для внутреннего использования.)

Другие вопросы по тегам