[С++] Передать метод в качестве аргумента.

VGordeev

Подскажите, плз, кто знает, с чем может быть связано:
Есть функция, которая принимает указатель на функцию. Она описана в некотором .cpp файле.
Далее, есть класс, в котором есть некоторые методы. Создаю объект. Пытаюсь передать метод в качестве входного аргумента (пишу объект->метод а компилятор ругается, говорит, что это не указатель. В то же время, если в этом cpp создать функцию и передать в качестве параметра первой функции, то всё ок.
Надеюсь на ваши знания и понимание (не ржать!)

maggi14

Так метод - это же не совсем функция. В частности, как компилятор должен узнать, чьего объекта этот метод? Ты же с ним дальше собираешься работать не как с указателем, а как с объектом

ppplva

Указатели на методы описываются как &Class::method, и это вовсе не указатели на функции.

freezer

ну можно использовать, например std::mem_fun* из хедера <functional>

ppplva

mem_fun - это объект

freezer

еще какой!
но в отличие от указателя на фанкцию или функцию-член инкапсулирует именно то что нужно (т.е. объект к которому будет применяться метод)
PS точнее не объект, а шаблон

ppplva

Ну в смысле, запись memfun(&Class::method) - это объект.
Конечно, так и надо сделать.

freezer

точнее надо bind1st(mem_fun(&T::f obj)

VGordeev

Хм... А приведите пример кода, где используется mem_fun?
Может, имеет смысл сделать методы статическими?

freezer

 Example
// functional_mem_fun.cpp
// compile with: /EHsc
#include <vector>
#include <functional>
#include <algorithm>
#include <iostream>

using namespace std;

class StoreVals
{
int val;
public:
StoreVals ( ) { val = 0; }
StoreVals ( int j ) { val = j; }

bool display ( ) { cout << val << " "; return true; }
int squareval ( ) { val *= val; return val; }
int lessconst ( int k ) {val -= k; return val; }
};

int main( )
{
vector <StoreVals *> v1;

v1.push_back( &StoreVals ( 5 ) );
v1.push_back( &StoreVals ( 10 ) );
v1.push_back( &StoreVals ( 15 ) );
v1.push_back( &StoreVals ( 20 ) );
v1.push_back( &StoreVals ( 25 ) );

cout << "The original values stored are: " ;
for_each( v1.begin( v1.end( mem_fun<bool, StoreVals> ( &StoreVals::display ) );
cout << endl;

// Use of mem_fun calling member function through a pointer
// square each value in the vector using squareval ( )
for_each( v1.begin( v1.end( mem_fun<int, StoreVals> ( &StoreVals::squareval ) );
cout << "The squared values are: " ;
for_each( v1.begin( v1.end( mem_fun<bool, StoreVals> ( &StoreVals::display ) );
cout << endl;

// Use of mem_fun1 calling member function through a pointer
// subtract 5 from each value in the vector using lessconst ( )
for_each( v1.begin( v1.end(
bind2nd ( mem_fun1<int, StoreVals,int> ( &StoreVals::lessconst 5 ) );
cout << "The squared values less 5 are: " ;
for_each( v1.begin( v1.end( mem_fun<bool, StoreVals> ( &StoreVals::display ) );
cout << endl;
}
Output
The original values stored are: 5 10 15 20 25
The squared values are: 25 100 225 400 625
The squared values less 5 are: 20 95 220 395 620


взято из мсдн

VGordeev

Ввиду отсутствия МСДНа и возможности его пока поставить, не мог бы ты хотя бы кратко сказать, как вообще работает эта функция mem_fun?

freezer

поищи стандарт Си++ или Страуструпа какого-нить в локалке. В общем, идея такая: та передаешь в функцию mem_fun один параметр: указатель на функцию-член, тебе возвращают объект для которого переопределена операция "круглые скобки", т.е. ты этот объект можешь использовать как функцию (т.н. функционал) с 2-мя аргументами: указатель на объект и аргумент твоего метода. Функция bind1st работает аналогично, у нее 2 параметра: бинарный функционал и значение для 1-го операнда, результат - унарный функционал (т.е. объект который можно использовать как функцию с 1-м параметром)

rosali

Я фигею, человек в простых вещах путается, а вы ему mem_fun, bind1st
На самом деле так:
Если объявить метод статическим, проблемы действительно исчезнут. Но правда ли, что именно это тебе нужно, то есть правда ли, что этот метод не зависит от объекта.
В действительности, просто указатель на функцию нужен редко, функций в программе конечное число так что ему одному выразительности крайне не хватает. В Си как правило вместе с указателем на функцию передают "контекст", то есть некоторое void * которое эту функцию параметризует.
Например, вместо такой функции высшего порядка:
 
typedef int (*fun_tint n);
int sum(fun_t f, int n1, int n2)
{
int s = 0;
for(int n=n1;n<n2;n++)
s+= f(n);
return(s);
}
намного правильнее писать такую
 
typedef int (*fun_tvoid * context, int n);
int sum(fun_t f, void * context, int n1, int n2)
{
int s = 0;
for(int n=n1;n<n2;n++)
s+= f(context, n);
return(s);
}
Потому что в первом случае ты сможешь применить sum к таким функциям как f(x) = x+1, f(x) = x+2, но НЕ сможешь применить ее к f(x) = x+z, где z пользователь вводит с клавиатуры.
Чтобы прибавить эстетизма этому void * context, можно воспользоваться C++-ными member functions, но это довольно ущербно, потому что делегатов в С++ нет... Ну что-то это я уже умничать начинаю, того гляди тоже до template-ов дойду Лучше остановлюсь на том, что если ты не можешь объявить метод статическим, то сделай по такому принципу:
 
class C
{
int y;
public:
C(int y):y(y){}
int m(int x)
{
return(x+y);
}
}
int C_m(void * ths, int x)
{
return( C*)ths)->m(x) );
}
...
C c(5);
int s = sum(C_m, &c, 0, 10);
...
Я это все не компилировал, но на первый взгляд грамматических ошибок нет...

VGordeev

Спасибо конечно, но это несколько не то. Просто в твоём примере не передаётся именно указатель на метод. Меня же интересует именно это. Например, такой код не компилится:
#include <iostream>
using namespace std;
typedef int (*funcPointerint number);
class Test
{
private:
int a;
public:
Test
{
a=5;
}
~Test
{
}
int calc(int number)
{
return a+number;
}
};
int FailureFunc(funcPointer classFunction, int number)
{
classFunction(number);
}
int main(void)
{
Test tst;

cout << FailureFunc(tst.calc, 10) << endl;

return 0;
}
Компилятор ругается: cannot convert parameter 1form int(int) to funcPointer
Может, я чего здесь намудрил?
P.S. А указатели на функции используются довольно часто. Как обработчики событий. Например, в glut, откуда данный пример и вытек. Я не могу передать в glutDisplayFunc(...) указатель на метод некоторого класса.
Вот, мучаюсь. Может, поможет кто.
P.P.S. Да, насчёт mem_func - это, всё-таки, немного не то. Просто функционалы обычно используются в контейнерах. mem_func(... в частности, для вызовов методов класса с параметрами внутри for_each(...). Я не могу понять, как их можно использовать вне их. Никогда сталкиваться не приходилось - опыта нет А в мертвечинке все примеры основаны на использовании for_each(...)
В общем, кто может помочь - буду благодарен.
P.P.S. Сорри, что класс не выровнен - я форматировал, не могу понять, почему собщение опять воровнялось по левому классу.
P.P.P.S. Да, со статическими методами - это я был, конечно, неправ.

ppplva

Здесь статический метод явно не покатит, в FailureFunc нужен информация о конкретном объекте.
Если можно изменять FailureFunc, то разумнее передать туда указатель на объект - то есть функция будет вызывать метод calc(int) любого объекта.
Если хочется вызывать произвольный метод произвольного объекта, можно вдобавок передать еще указатель на метод. Или, более кошерное решение через mem_fun:
#include <iostream>
#include <functional>
using namespace std;

class A {
int a_;
public:
A : a_(5) {}
A(int a) : a_(a) {}
int calc(int n) {
return a_+n;
}
};


template <class T>
void f(T t, int number)
{
cout << t(number) << endl;
}


int main
{
A a(13);
f(bind1st(mem_fun(&A::calc&a 1);
return 0;
}
C glutDisplayFunc сложнее, там передается всего лишь указатель на функцию.
Значит, авторы api считают, что ей не должна требоваться дополнительная информация. Может у тебя проблемы в дизайне ?
Максимум, что приходит в голову - передавать данные (указатель на объект, например) через глобальную переменную.

VGordeev

Да, сейчас почитал на РСДН. Действительно, придётся редизайнить. Просто не хотелось делать глобальных переменных, классов. Но придётся. Просто это неправильно. Можно ещё сделать, в частности, так: написать несколько функций в main.cpp, которые скармливать glutDisplayFunc и иже с ней. А уже в них вызывать необходимые методы. Эх, а так хотелось ООП...

ppplva

Можно ещё сделать, в частности, так: написать несколько функций в main.cpp, которые скармливать glutDisplayFunc и иже с ней. А уже в них вызывать необходимые методы. Эх, а так хотелось ООП...
Сделай их статическими методами какого-нибудь класса

VGordeev

Угу. Лучше вообще всё сделать процедурно-глобальным
Да нет, я пошёл по описанному мной пути. Криво, но ладно...

bleyman

Смешной ты Меф.
Глют процедурно-ориентированный. А ты попытался его явно включить в ОО модель. Так не бывает.

Helga87

Почему?
Есть, например, GLOW - объектно-ориентированная обертка над Глютом.

sergey_m

Ввиду отсутствия МСДНа
В FAQ есть поиск по MSDN.

bleyman

Я верю =) Но он-то сам пишет "обёртку над глютом", по ходу.

Helga87

Так здесь ситуация из разряда "а нафига", а не из разряда "ну ты и смешной".

rosali

Если хочется вызывать произвольный метод произвольного объекта, можно вдобавок передать еще указатель на метод
Да говорю же, нет в С++ делегатов. Можно будет вызвать
произвольный метод произвольного объекта
_конкретного_ класса.
Максимум, что приходит в голову - передавать данные (указатель на объект, например) через глобальную переменную
Есть еще такое папское слово "thunk"! Санка - это коротенькая функция, из 2-3-х ассемблерных инструкций, создаваемая во время выполнения программы. например:
 
typedef int (* func_with_context_tint, void *);
typedef int (* func_tint);
CreateThunk(void * context, func_with_context_t f)
{
void * p = malloc(10);
//дальше ботаешь opcode-ы x86-assembler-а и по адресу p фигачишь такие комманды:
// mov edx, <константа, равная context>
// jmp <константа, равная f>
//потом
returnfunc_t) p);
}
Ну и кажись для __fastcall конвенции будет работать Для __stdcall надо mov edx заменить на push, а для __ccall надо еще и вместо jmp написать call, а потом ret. Ну, я мог где-то ошибиться, но в целом идея понятная и проверено, что она работает... Переносимость правда страдает, эта CreateThunk должна быть разная для разных архитектур.
PS 2 mef. Код выравнивают с помощью [_code_] ... [_\_code_] (только без "_").

ppplva

Да говорю же, нет в С++ делегатов. Можно будет вызвать
произвольный метод произвольного объекта
_конкретного_ класса.
А я и не говорил про произвольный класс.
И вообще, шаблоны рулят.
Есть еще такое папское слово "thunk"! Санка - это коротенькая функция, из 2-3-х ассемблерных инструкций, создаваемая во время выполнения программы.
Ну это только если совсем припрет.

Helga87

Насчет делегатов на C++. Есть такая статья , в которой показано как сделать делегаты.
К слову, там не используется ни mem_fun, ни bind1st, ни генерация кода в рантайме, зато активно юзаются шаблоны.

yolki

в дельфях можно например вот так:


type
PFunc = function (...):integer of object;

procedure F(PF: PFunc);
begin
end;

var SomeObj : TObj;
begin
F(SomeObj.Method);
end.

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