Понижение указателя на функцию-член. Это законное использование?

Я храню список указателей на функцию-член в массиве. Я хочу проиндексировать массив и выполнить соответствующую функцию. Будет много массивов со списком функций из разных классов (все производные от Base), поэтому класс не известен во время компиляции.

У меня работает схема, но я не совсем доволен необходимостью использовать указатель void в одном месте, но я не могу этого избежать.

Является ли мое приведение между базовыми и производными указателями функций-членов законным в соответствии со стандартом С++ 11 (он работает с g++). Буду признателен за консультацию языкового юриста!

Ниже приведена урезанная, но работоспособная версия моего кода.

#include <iostream>

using std::cout;

//*************************************

class Base {
public:

  typedef int (Base::*BaseThunk)();

  virtual int execute(BaseThunk x) {return 0;}

};

//*************************************

class Derived : public Base {
public:

  typedef int (Derived::*DerivedThunk)();

  int execute(BaseThunk step) {
    return (this->*step)(); //Is this OK ? step is really a DerivedThunk.
  }

  int f1() { cout<<"1\n";return 1;}
  int f2() { cout<<"2\n";return 2;}
  int f3() { cout<<"3\n";return 3;}

  static DerivedThunk steps[];
};

//Here is an array of pointers to member functions of the Derived class.
Derived::DerivedThunk Derived::steps[] = {&Derived::f1, &Derived::f2, &Derived::f3};

//*************************************

class Intermediate : public Base {
public:

  void f(void *x) { //I am worried about using void pointer here !

    BaseThunk *seq = reinterpret_cast<BaseThunk *>(x);

    Derived d;
    d.execute(seq[2]);
  }

};

//*************************************

int main() {
  Intermediate b;

  b.f(&Derived::steps);
}

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

François Andrieux 10.06.2019 20:15

Функция f получает указатель на массив DerivedThunk::* (который я считаю обычным указателем данных) в void*. Затем он переинтерпретирует void* как BaseThunk*, чтобы разрешить его индексацию. Я думаю, что часть массива для указателя является законной, но неявно предполагается, что BaseThunk и DerivedThunk имеют одинаковый размер. Не уверен, что это всегда будет правдой.

user1759557 10.06.2019 23:34

Здесь вы можете использовать static_cast (что помогает избежать путаницы с указателями объектов против. указателями на члены). Однако это не отвечает на ваш вопрос, поскольку void* все еще задействован, как вы сказали.

Davis Herring 11.06.2019 03:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
3
159
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваше беспокойство по поводу void* вполне обосновано: это неопределенное поведение, потому что (в Intermediate::f) вы выполняете арифметику указателя и считываете указатель, который не соответствует типу массива.

Хорошей новостью является то, что есть простое решение: поскольку цель ваших массивов состоит в том, чтобы функции производного класса вызывались с учетом только Base& и BaseThunk, вы можете хранить этого типа:

Base::BaseThunk Derived::steps[]=
  {static_cast<BaseThunk>(&Derived::f1),
   …};

static_cast несколько многословны, но полностью законны, если вы использовать результирующие BaseThunk объекты с объектом, тип которого является производным от Derived. Вам даже не нужно сначала получать Derived*:

int Base::execute(BaseThunk x)  // no need to be virtual
{return (this->*x)();}

Спасибо. Я не думал об использовании static_cast в определении массива. Дэвис Херринг, кажется, думал в том же направлении. Ваше решение отлично работает для меня, так как массивы все равно будут генерироваться машиной (проблема связана с компилятором, который генерирует исходный код C++), и мне все равно, будет ли он подробным. «Выполнение» объявлено виртуальным, потому что оно должно быть виртуальным в моей реальной проблеме. Я должен был удалить его из кода игрушки, который я разместил.

user1759557 12.06.2019 04:17

@user1759557: Я бы сказал, что Дэвис Херринг думал в том же направлении, что и очень, хотя речь шла только о reinterpret_cast. Я рад, что этот подход работает для вас, хотя приведение всегда можно поместить в функцию с коротким именем для удобства ввода/чтения.

Davis Herring 12.06.2019 04:22

Извините, Дэвис, возможно, я не совсем понял ваш комментарий, когда читал его. Я думал, вы говорите, что я могу заменить reinterpret_cast для void* в «Intermediate» на static_cast. Теперь кажется, что вы имели в виду массив, но я не понял его с первого раза. Я использовал #define, чтобы сократить код, но ваша идея использования функции намного изящнее, и, конечно, компилятор ее оптимизирует.

user1759557 16.06.2019 03:08

@ user1759557: Нет, вы были правы — я сначала прокомментировал состав, который вы использовали, а затем написал ответ о том, какой состав будет работать.

Davis Herring 16.06.2019 03:39

Дох! Я не заметил, что правильный ответ был от того же человека (вы), который также прокомментировал. Я пытался отдать должное комментарию.

user1759557 17.06.2019 02:30

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