Указатель функции-члена С++ с разными аргументами - или это все равно плохо?

Хотя я боюсь, что вы скажете мне, что эта тема обсуждалась несколько раз, я осмеливаюсь спросить об этом, так как я не смог найти решение. Наверное, я просто искал не то...

Предположим, что у меня есть функция, которая получает «режим» от какой-то внешней функции. В зависимости от режима функция будет вызывать разные функции-члены одного и того же объекта. Это хорошо работает для меня с функцией-членом без каких-либо аргументов, но я не нашел, как расширить ее на члены с аргументами. В реальном приложении аргументами являются не int/float, а более сложные классы, и вызов вложен в разные циклы, поэтому мне нужно было бы несколько раз помещать операторы switch, что я считаю уродливым.

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

Вопрос B: Это совершенно неправильный/плохой подход? Как бы мне сделать это лучше?

Большое спасибо за вашу помощь и пояснения.

Крис

выдержка из заголовка:

typedef void (Object::*memberFunction)();

class Object
{
    void memberFnNoArg();
    void memberFnWithIntArg(int arg);
    void memberFnWithFloatArg(float arg);
}

выдержка из cpp:

void function()
{
    int mode = getModeFromSomewhere();

    int intArg = 33;
    float floatArg = 66.6;

    switch(mode)
    {
    case 1:
        process(&Object::memberFnNoArg);
        break;
    case 2:
        process(&Object::memberFnWithIntArg, ???); // how can I pass arg?
        break;
    case 3:
        process(&Object::memberFnWithFlaotArg, ???); // how can I pass arg?
        break;
    default:
        // do nothing;
    }

}

void process(Object::memberFunction func)
{
    Object object;
    // loops, called several times, ...
    (object.*func)(); // how do I handle different arguments?
}

person Chris    schedule 04.06.2013    source источник
comment
Может быть, здесь поможет std::bind?   -  person BoBTFish    schedule 04.06.2013
comment
Может быть, использовать шаблон?   -  person Barmar    schedule 04.06.2013
comment
Я не уверен, как указатели на функции-члены упрощают ваш дизайн. Кажется, что лучше было бы превратить алгоритм, использующий объект, в функтор.   -  person Ben Voigt    schedule 05.06.2013
comment
@ben-voigt: это упростило бы мой дизайн, потому что внутри process() функция-член вызывается в нескольких местах внутри алгоритма. В противном случае мне пришлось бы передавать режим и другие аргументы в process(), а затем различать каждый раз. Я ищу более прозрачный дизайн...   -  person Chris    schedule 05.06.2013


Ответы (7)


Оборачивать алгоритм в функтор — правильный подход, а std::function — хороший функтор, предоставляемый стандартной библиотекой.

Но использование boost::bind или даже std::bind, как предлагает Томек, действительно уродливо, IMO, и быстро выходит из-под контроля при связывании нескольких аргументов.

Если у вас есть последний компилятор, вы можете вместо этого использовать лямбду, что делает пример Томека похожим на:

std::function<void(Object*)> f  =
    [](Object* const that){ that->memberFnNoArg(); };

int int_value = 22;
std::function<void(Object*)> f2 =
    [int_value](Object* const that){ that->memberFnIntArg(int_value); };

Object o;
f(&o);
f2(&o);

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

Конечно, вы можете сделать параметр ссылкой на объект, если очень хотите, но я предпочитаю здесь указатели.

person Ben Voigt    schedule 05.06.2013
comment
Спасибо за это очень элегантное предложение! Еще один вопрос: (Как) я могу заставить это работать также с аргументами указателя? Я получил ошибку «указатель» не захвачен. Есть ли что-то вроде std::ref() для указателей? - person Chris; 05.06.2013
comment
@Chris: Если вы хотите передать аргумент, который является переменной, а не буквальной константой, укажите переменную между []. Таким образом, лямбда сохранит свое значение для последующего использования во время вызова функции-члена. - person Ben Voigt; 05.06.2013

Взгляните на std::function и std::bind, кажется, они идеально подходят для того, что вам нужно.

РЕДАКТИРОВАТЬ:

std::function<void(Object &)> f = &Object::memberFnNoArg;
std::function<void(Object &)> f2 = std::bind(&Object::memberFnWithIntArg, _1, 22);

Object o;
f(o);
f2(o);

должно работать из коробки, насколько я помню. Это то, что тебе надо?

person Tomek    schedule 04.06.2013
comment
Я рад слышать о привязке, потому что я думал об этом, но не был уверен в этом. На данный момент я борюсь с передачей только функции-члена без этого указателя для привязки... Я отредактировал проблему, чтобы проиллюстрировать проблему: я хочу иметь возможность передать используемую функцию-член до создания экземпляра объекта. - person Chris; 04.06.2013
comment
@Chris: вы можете связать любой аргумент, а не только целевой объект. - person Ben Voigt; 05.06.2013
comment
@Tomek: это выглядит многообещающе, но я до сих пор не понимаю, как обрабатывать функции-члены с аргументами. Допустим, я выполняю назначение std::function внутри оператора switch. Затем мне нужно будет передать функцию в качестве аргумента для «недействительного процесса ()». Я пытался сделать это с назначением auto f1 = std::bind(&Object::memberFnWithIntArg, 33);, но тип f1 не совпадает с f, поэтому я не могу обрабатывать f, f1 и f2 с одной и той же функцией процесса... - person Chris; 05.06.2013
comment
@Chris: Проверьте мои правки. Обратите внимание, что я не уверен, что правильно понял (я не помню точного использования std::bind). Ключевое слово, которое вы, вероятно, ищете, это «заполнитель». - person Tomek; 05.06.2013
comment
Вам не нужно использовать mem_fn или что-то подобное здесь? - person Ben Voigt; 05.06.2013
comment
@Ben: я не уверен, давно им не пользовался. Я считаю, что std::function и std::bind заменяют, а не расширяют mem_fn. Вот несколько примеров: en.cppreference.com/w/cpp/utility/ функциональный/связать - person Tomek; 06.06.2013

Вы можете использовать функцию шаблона varadic:

template <typename... Args>
void process(void (Object::*func)(Args...),Args... args)
{
    Object object;

    // loops, called several times, ...
    (object.*func)(args...);
}

Вот полный пример:

#include <iostream>

struct Object
{
    void memberFnNoArg()
    {
      std::cout << "Object::memberFnNoArg()\n";
    }

    void memberFnWithIntArg(int arg)
    {
      std::cout << "Object::memberFnWithIntArg(" << arg << ")\n";
    }

    void memberFnWithFloatArg(float arg)
    {
      std::cout << "Object::memberFnWithFloatArg(" << arg << ")\n";
    }
};

template <typename... Args>
void process(void (Object::*func)(Args...),Args... args)
{
    Object object;

    // loops, called several times, ...
    (object.*func)(args...);
}

int main()
{
  process(&Object::memberFnNoArg);
  process(&Object::memberFnWithIntArg,5);
  process(&Object::memberFnWithFloatArg,2.7F);
  return 0;
}
person Vaughn Cato    schedule 04.06.2013

Один из способов, который я вижу, - это использовать переменные аргументы (почти как printf, sprintf делает это). (Или, может быть, с библиотеками stdc, передавая список разных типов.)

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

Ниже приведен простой (не членский) пример того, как получить переменные аргументы (функции-члены будут работать точно так же). См. stdarg.h.

typedef void (*var_function)(int typearg, ...);

void print_arg(int typearg, ...)
{
  va_list ap;
  int i;

  va_start(ap, typearg); 

  if (typearg==1) { // int 
     int i= va_arg(ap, int);
     printf("%d ", i);
  }
  else 
  if (typearg==2) { // float 
     float f= va_arg(ap, float);
     printf("%f ", f);
  }
  else 
  if (typearg==3) { // char *
     char *s= va_arg(ap, char *);
     printf("%s ", s);
  }

     ....

  va_end(ap);
}

// calling function with different types
int main()
{
   print_arg(1, 999);
   print_arg(2, 3.1415926);
   print_arg(3, "Hello");
   ....
   process(print_arg, 3, "via pointer);
person Nicholaz    schedule 04.06.2013
comment
Хотя это можно заставить добиться желаемого эффекта, я бы поднялся над своим мертвым телом, чтобы просмотреть. - person Balog Pal; 04.06.2013
comment
Спасибо за предложение, но это решение, похоже, не упрощает мою задачу... оно так же непрозрачно, как и оператор switch. - person Chris; 04.06.2013

Похоже на packaged_task. Также ознакомьтесь с предложением Томека.

Хотя в реальной жизни я бы задал много вопросов о том, зачем вам это вообще нужно. Возможно, ваша работа может быть лучше освещена с помощью std::future или другого средства более высокого уровня,

person Balog Pal    schedule 04.06.2013
comment
Спасибо за ответ. Может быть, я просто не понимаю, но я думаю, что packaged_task и future помогают с проблемами многопоточности, о которых этот вопрос не говорит. - person Chris; 04.06.2013
comment
future может быть запущен синхронно или асинхронно — смысл может быть очень близок к тому, что вы хотите, создать заказ и позже получить результат. То, что он делает между ними, просто волшебство. - person Balog Pal; 04.06.2013

Не может ли каждая функция (memberFn**) быть членом классов аргументов?

class BaseArg
{
  virtual void Fn() = 0;
};

class IntArg : public BaseArg
{
  void Fn();
};

class FloatArg : public BaseArg
{
  void Fn();
};


void function()
{
    int mode = getModeFromSomewhere();
    BaseArg* pArg;

    if ( mode ... ){
      pArg = new IntArg( 33 );
    }
    else {
      pArg = new FloatArg( 66.6 );
    }

    pArg->Fn();  // Call the right function without a switch
                 // and without knowing the arguments

}
person iksess    schedule 04.06.2013

То же, что и другие ответы, но для отображения методов-членов:

#include <iostream>
class Object
{
public:
    void memberFnNoArg()
    {
        std::cout << "Object::memberFnNoArg()\n";
    }

    void memberFnWithIntArg(int arg)
    {
        std::cout << "Object::memberFnWithIntArg(" << arg << ")\n";
    }

    void memberFnWithFloatArg(float arg)
    {
        std::cout << "Object::memberFnWithFloatArg(" << arg << ")\n";
    }
    bool memberFnWithBoolReturn(int)
    {
        return true;
    }
    template <typename... Args>
    void process(void (Object::*func)(Args...),Args... args);
    // overload process
    template <typename... Args>
    bool process(bool (Object::*func)(Args...),Args... args);
};
template <typename... Args>
void  process( void (Object::*func)(Args...),class Object* obj,Args... args)
{

    (obj->*func)(args...);
}
template <typename... Args>
bool  process( bool (Object::*func)(Args...),class Object* obj,Args... args)
{
    return ((obj->*func)(args...)) ;

}
int main()
{
    Object object;
    process(&Object::memberFnNoArg,&object);
    process(&Object::memberFnWithIntArg,&object,5);
    process(&Object::memberFnWithFloatArg,&object,2.7F);
    // overloaded process
    printf("%d\n",process(&Object::memberFnWithBoolReturn,&object,1));

    return 0;
}
person Mohammad Kanan    schedule 15.02.2017