передать функцию-член класса в качестве аргумента другой функции-члена того же класса - PullRequest
0 голосов
/ 06 февраля 2019

Я предоставил более простую версию проблемы, с которой я имею дело.

У меня есть класс Entity, который имеет map объекта user.Для простоты я добавил только один элемент (name).Я реализовал getter и setter вокруг объекта userMap.

Мне необходима функция wrapper, чтобы справиться с критической ситуацией из-за многопоточного случая.(не показано здесь).Я как-то реализовал рабочий пример, пройдя несколько SO-потоков.

Но, как вы можете видеть, я должен написать (id,arg) аргумент кортежа в трех местах при использовании функции wrapper внутри функции procedure.Есть ли способ реализовать ту же функциональность, имея какой-то заполнитель для аргументов функции, так что вызов оболочки выглядит чисто, например wrapper(1,static_cast<void*>(&name),function_ptr).

#include <iostream>
#include <functional>
#include <map>
using namespace std;
class Entity {
    public:
        struct user {
            int id;
            string name;
        };
        map<int,user> userMap;

        void getUserName(int id,void * arg);
        void setUserName(int id,void * arg);
        void procedure();
        void wrapper(int id,void*,std::function<void(int,void*)>);
};

void Entity::getUserName(int id,void *arg) {
    auto iter = userMap.find(id);

    if(iter != userMap.end()) {
        *static_cast<string*>(arg) = iter->second.name; 
    }
}

void Entity::setUserName(int id,void *arg) {
    auto iter = userMap.find(id);
    if(iter != userMap.end()) {
        iter->second.name = *static_cast<string*>(arg);
    }
}

void Entity::wrapper(int id,void *arg,std::function<void(int,void*)> func) {
    cout <<"\nSome Other critical task based on id"<< endl;

    // then call the callback
    func(id,arg);
}

void Entity::procedure() {
    cout <<"Procedure starts"<<endl;

    user u = {};
    u.id = 1;
    u.name = "abc";
    this->userMap[1] = u;

    string name;
    wrapper(1,static_cast<void*>(&name),[this](int id,void*arg){
        getUserName(id,arg);
    });
    cout <<"name :"<< name << endl;

    cout <<"\nSome processing..."<<endl;

    name = "def";
    wrapper(1,static_cast<void*>(&name),[this](int id,void*arg){
        setUserName(id,arg);
    });

    cout <<"\nSome more processing..."<<endl;

    wrapper(1,static_cast<void*>(&name),[this](int id,void*arg){
        getUserName(id,arg);
    });

    cout <<"name :"<< name << endl;

    cout <<"Procedure ends"<<endl;
}

int main() {
    Entity E;
    E.procedure();
}

Вывод:

Some Other critical task based on id
name :abc

Some processing...

Some Other critical task based on id

Some more processing...

Some Other critical task based on id
name :def
Procedure ends

Спасибо!

Ответы [ 2 ]

0 голосов
/ 07 февраля 2019

Я начну с прямого ответа, но, пожалуйста, прочитайте следующее предупреждение:

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

void wrapper(int id, void* arg, void (Entity::* func)(int,void*))
{
    cout <<"\nSome Other critical task based on id"<< endl;
    (this->*func)(id, arg);
}

Тогда ваши вызовы станут проще:

wrapper(1, static_cast<void*>(&name), &Entity::getUserName);
wrapper(1, static_cast<void*>(&name), &Entity::setUserName);

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

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

void criticalStuff(int id)
{
    cout <<"Some critical task based on id\n";
}

void Entity::getUserName(int id, string & arg)
{
    criticalStuff(id); // Critical stuff first.

    auto iter = userMap.find(id);
    if(iter != userMap.end()) {
        arg = iter->second.name; 
    }
}

void Entity::setUserName(int id, const string & arg)
{
    criticalStuff(id); // Critical stuff first.

    auto iter = userMap.find(id);
    if(iter != userMap.end()) {
        iter->second.name = arg;
    }
}

Любой, кто добавляет новые поля, все равно должен помнить критические вещи при написании новых методов получения и установки, но более распространенный случай (вызов этих функций) будет обрабатывать критические вещи автоматически.Кроме того, вы можете вернуться к проверке типов параметров, поскольку больше нет причин приводить вещи к void *.

0 голосов
/ 06 февраля 2019

По моему мнению, вы вызываете проблемы, заставляя использовать lambda, я знаю, что они интересны, но часто не очень полезны.В общем, держите это просто глупо (KISS) и в вашем случае просто разделите функции на две отдельные функции.

class Entity {
    public:
        ...            
        void getUserName(int id,void * arg);
        void setUserName(int id,void * arg);
        void procedure();
        void criticalTask(int id); // critical task, based on id
};

void Entity::getUserName(int id,void *arg) {...}

void Entity::setUserName(int id,void *arg) {...}

void Entity::criticalTask(int id) {
    cout <<"\nSome Other critical task based on id"<< endl;
}

void Entity::procedure() {
    cout <<"Procedure starts"<<endl;

    ...

    criticalTask(id);
    getUserName(id, &name);

    ...

}

int main() {
    Entity E;
    E.procedure();
}
...