Как вызвать указатель на функцию-член из статической функции-члена?

Мне нужно получить функцию-член, вызываемую указателем стандартной функции, поэтому я попытался абстрагироваться от таких вещей:

class Sample {
public:
    virtual void doSomething(void) = 0;
};


class A : public Sample {
    void doSomething(void);     // details omitted
};

class B : public Sample {
    void doSomething(void);     // details omitted
};


class Executor {
public:
    Executor(Sample *sample)
     : func(&sample->doSomething)
    {
    }

    static void *execute(void *data) {
        Executor *pX = data;

        (pX->*func)();          // error invalid access of func from static function

        (pX->*pX->func)();      // error pointer to member type 'void (Sample::)()'
                                //       incompatible with object type 'Executor'
    }

private:
    void (Sample::*func)(void);
};



int main(void) {
    A   myA;
    B   myB;
    Executor x0(&myA);
    Executor x1(&myB);

    externallyInvoke(&Executor::execute, &x0);
    externallyInvoke(&Executor::execute, &x1);
}

externallyInvoke - это системный вызов Linux, который принимает указатель функции и указатель данных. Я хотел бы использовать статическую функцию-член вместе с указателем this в качестве данных.

... и я не хочу, чтобы классы типа A или B имели статические члены. Итак, моя идея состояла в том, чтобы создать интерфейс, подобный классу Sample, который расширяется на A и B.

Моя проблема в том, что я не знаю, как вызвать указатель на функцию-член изнутри функции Executor::execute.


person django013    schedule 28.10.2014    source источник
comment
не может получить доступ к переменной-члену из статической функции - что неясно?   -  person BЈовић    schedule 28.10.2014


Ответы (3)


Проблема в том, что вам нужны два объекта внутри execute: один - это экземпляр Executor, который будет предоставлять func, а другой - экземпляр (класса, производного от) Sample, для которого будет вызываться func. Таким образом, вы должны хранить объект внутри Executor, а не функцию:

class Executor {
public:
    Executor(Sample *sample)
     : obj(sample)
    {
    }

    static void *execute(void *data) {
        Executor *pX = static_cast<Executor*>(data);

        pX->obj->doSomething();
    }

private:
    Sample *obj;
};


int main() { // note that `void main()` is not legal C++
    A   myA;
    B   myB;
    Executor x0(&myA);
    Executor x1(&myB);

    externallyInvoke(&Executor::execute, &x0);
    externallyInvoke(&Executor::execute, &x1);
}

Указатель на функцию-член (например, ваш исходный void (Sample::*func)()) идентифицирует функцию внутри класса, но не сохраняет объект. Вам все равно нужно предоставить один для вызова функции.

person Angew is no longer proud of SO    schedule 28.10.2014
comment
не должно x0(myA) быть x0(&myA)? - person wimh; 28.10.2014
comment
@Wimmel Да, конечно, я скопировал это из OP до того, как это было исправлено в вопросе. Спасибо. - person Angew is no longer proud of SO; 28.10.2014
comment
@Angew Я, очевидно, хотел заново изобрести функтор, но ваше решение намного чище и для этого: lovely :) THX - person django013; 30.10.2014

Если вы хотите взаимодействовать с внешним системным вызовом, вам, по сути, придется изобретать std::function себя заново. Нет проблем, здесь, в Stack Overflow, мы мастера по переосмыслению существующих технологий. Так...

Во-первых, интерфейс:

struct FunctionStateBase
{
    virtual ~FunctionStateBase() {}
    virtual void Invoke() = 0;
};

extern "C" void InvokeAndDelete(void * data)
{
    auto state = static_cast<FunctionStateBase *>(data);
    state->Invoke();
    delete state;
}

Вот как вы это используете:

externallyInvoke(&InvokeAndDelete, MakeFunction(&A::doSomething, &myA));

Теперь нам нужно реализовать MakeFunction:

template <typename> struct FunctionState;

template <typename C, typename R>
struct FunctionState<R (C::*)()> : FunctionStateBase
{
    R (C::ptmf_*)();
    C * obj_;

    FunctionState(R (C::ptmf*)(), C * obj) : obj_(obj), ptmf_(ptmf) {}

    virtual void Invoke() { (C->ptmf_)(); }
};

template <typename C, typename R>
FunctionState<R (C::*)()> MakeFunction(R (C::*ptmf)(), C * obj)
{
    return new FunctionState<R (C::*)()>(ptfm, obj);
}

На этом этапе мы управляем временем жизни оболочки функции вручную и заметим, что InvokeAndDelete фактически становится владельцем состояния функции. В правильном C ++ мы бы обернули весь вызов системного вызова в класс, который бы инкапсулировал управление временем жизни внутри.

Вы можете добавить дополнительные специализации для функций-членов, которые принимают аргументы; вам просто нужно сохранить копию аргументов в состоянии.

person Kerrek SB    schedule 28.10.2014

Вам также необходимо передать экземплярSample для вызова функции (поскольку это указатель на член Sample). Есть несколько способов принести экземпляр с собой. Вы можете сделать его членом Executor, передать std::pair* как data или объединить указатель функции и экземпляр в качестве функтора. Вот подход на основе ламды для последнего. Ламда имеет то преимущество, что она более универсальна. Можно сделать гораздо больше, чем просто вызвать одного члена одного класса. В качестве бонуса этот подход не позволяет избежать правил видимости, хотя это означает, что doSomething не может быть закрытым (или его нужно вызывать через родительский указатель).

template<class F>
class Executor {
    F f;
public:
    Executor(F f): f(f){}
    static void *execute(void *data) {
        Executor<F> *pX = static_cast<Executor<F>*>(data);
        pX->f();
        return this; // not quite sure what you intend to return, but just to make this a well formed function...
    }
};


int main() {
    A   myA;
    B   myB;
    auto callback0 = [myA]{
        myA.doSomething();
    };
    auto callback1 = [myB]{
        myB.doSomething();
    };
    Executor<decltype(callback0)> x0(callback0);
    Executor<decltype(callback1)> x1(callback1);

    externallyInvoke(&Executor::execute, &x0);
    externallyInvoke(&Executor::execute, &x1);
}
person eerorika    schedule 28.10.2014
comment
func является членом, его нельзя использовать в static методе. - person user657267; 28.10.2014
comment
@ user657267 а, попался. Исправил ответ. - person eerorika; 28.10.2014
comment
Как это исправить ответ? Он хочет вызвать указатель на функцию-член над static методом, это просто невозможно, поэтому public ничего не меняет. - person user657267; 28.10.2014
comment
@ user657267, блин не мой день. Я не заметил, что это был не только член, но и указатель на функцию-член другого класса. - person eerorika; 28.10.2014