Я храню список указателей на функцию-член в массиве. Я хочу проиндексировать массив и выполнить соответствующую функцию. Будет много массивов со списком функций из разных классов (все производные от 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);
}
Функция f получает указатель на массив DerivedThunk::* (который я считаю обычным указателем данных) в void*. Затем он переинтерпретирует void* как BaseThunk*, чтобы разрешить его индексацию. Я думаю, что часть массива для указателя является законной, но неявно предполагается, что BaseThunk и DerivedThunk имеют одинаковый размер. Не уверен, что это всегда будет правдой.
Здесь вы можете использовать static_cast
(что помогает избежать путаницы с указателями объектов против. указателями на члены). Однако это не отвечает на ваш вопрос, поскольку void*
все еще задействован, как вы сказали.
Ваше беспокойство по поводу 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: Я бы сказал, что Дэвис Херринг думал в том же направлении, что и очень, хотя речь шла только о reinterpret_cast
. Я рад, что этот подход работает для вас, хотя приведение всегда можно поместить в функцию с коротким именем для удобства ввода/чтения.
Извините, Дэвис, возможно, я не совсем понял ваш комментарий, когда читал его. Я думал, вы говорите, что я могу заменить reinterpret_cast для void* в «Intermediate» на static_cast. Теперь кажется, что вы имели в виду массив, но я не понял его с первого раза. Я использовал #define, чтобы сократить код, но ваша идея использования функции намного изящнее, и, конечно, компилятор ее оптимизирует.
@ user1759557: Нет, вы были правы — я сначала прокомментировал состав, который вы использовали, а затем написал ответ о том, какой состав будет работать.
Дох! Я не заметил, что правильный ответ был от того же человека (вы), который также прокомментировал. Я пытался отдать должное комментарию.
Обратите внимание, что указатели функций, указатели членов и указатели функций-членов не гарантируют совместимость с
void*
. Возможно, они могут быть больше, и приведение кvoid*
и обратно не гарантирует получение исходного значения. Приведение указателя функции к указателю объекта — это условно поддерживается, и не все реализации это позволяют.