Как задать членом класса указатель на функцию?

 
0
 
C++
ava
Нитонисе | 04.01.2013, 14:37
Пишу класс

class TMyClass
{
  public:
    void SetFunc(void *f) {func = f;}
    void DoFunc() {f()};
  private:
    void *func;
};

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

function1() 
{
  ShowMessage("function1");
}
function2() 
{
  ShowMessage("function2");
}


TMyClass *mc = new TMyClass;
mc->SetFunc(function1);
mc->DoFunc(); // выводит сообшение "function1"
mc->SetFunc(function2);
mc->DoFunc(); // выводит сообшение "function2"


Как это сделать?
Ответы (33)
ava
EvilsInterrupt | 04.01.2013, 14:55 #
Нитонисе,
1)

    void DoFunc() {f()};

Это верно? Если оформить по красивее, то:

    void DoFunc()
    {
        f()
    };


2)
Почему в setFunc присваиваете в переменную "void *func;", а в DoFunc() дергаете какую-то мифическую f() ?

3)
Может Вам изучить понятие "функтор" ? Другие названия этого термина "Объект функции". Это класс с переопределенным оператором "круглые скобки", чтобы объект этого класса вел себя как функция. Т.е. "все что выглядит как функция, ведет себя как функция = есть функция"
ava
Нитонисе | 04.01.2013, 15:58 #
С оформлением я не заморачивался, потому как писал прямо в форме ответа. Соответственно и опечатку в DoFunc допустил. А вот по п.3 видно надо изучать матчасть.
ava
NoviceF | 04.01.2013, 16:03 #
Цитата (Нитонисе @  4.1.2013,  14:37 findReferencedText)
Как это сделать? 

что-то похожее на то, что ты хочешь, есть в паттерне "Стратегия" http://ru.wikipedia.org/wiki/%D0%A1%D1%82%...BD%D0%B8%D1%8F)

вот кусок кода оттуда

int _tmain(int argc, _TCHAR* argv[])
{
        Client customClient;
        Strategy_1 str1;
        Strategy_2 str2;
        Strategy_3 str3;

        customClient.SetStrategy(&str1);
        customClient.UseStrategy();
        customClient.SetStrategy(&str2);
        customClient.UseStrategy();
        customClient.SetStrategy(&str3);
        customClient.UseStrategy();

        return 0;
}

Цитата (Нитонисе @  4.1.2013,  14:37 findReferencedText)
 А хочу я иметь членом класса некую функцию func, реализация которой может быть произвольной. 

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

class TMyClass
{
  public:
    void SetFunc(void *f) {func = f;}
    void DoFunc() {f()};
  private:
    void *func;
};

void *func; должен быть указателем на какую-то конкретную сигнатуру, и присваивать ему мы дожны функции с такой же сигнатурой, но, если требуется, различным поведением. Хотя на практике не сталкивался, но по идее должно работать smile
ava
mes | 04.01.2013, 18:36 #
Цитата (NoviceF @  4.1.2013,  15:03 findReferencedText)
 В случае с функцией, фиксированной должна быть сигнатура.

понятие функция может быть легко расширено до функционального обьекта и это расскрывает новые свободы, о чeм уже было сказано выше..
для ознакомления с подобной концепцией см.   [boost:: / (c++11)std::] function и bind..

Добавлено позднее:
Цитата (Нитонисе @  4.1.2013,  12:37 findReferencedText)
 А хочу я иметь членом класса некую функцию func, реализация которой может быть произвольной. 

для простой функции никаких наворотов не требуется, всe работает из коробки )
ava
EvilsInterrupt | 04.01.2013, 18:42 #
Нитонисе,
Поясни какую проблему ты решаешь? Для чего потребовался такой код? Возможно есть решение проще.
ava
mes | 04.01.2013, 18:42 #
Цитата (Нитонисе @  4.1.2013,  12:37 findReferencedText)
private:
  void *func;

вот набросок :

#include <iostream>

struct A {
   
   void (*some_fn)();
   
   void do_some () {
      some_fn();
   }
}; 
void f1 () { std::cout << 1; }
void f2 () { std::cout << 2; }

int main ()
{
   A a;
   a.some_fn = f1;   
   a.do_some();
   
   a.some_fn = f2;   
   a.do_some();   
}
ava
NoviceF | 04.01.2013, 20:26 #
 smile для понимания, пожалуйста, сделайте набросок, чего в данном примере можно добиться используя функтор, вместо указателя на функцию и почему требования к сигнатуре можно игнорировать.
ava
EvilsInterrupt | 04.01.2013, 20:47 #
NoviceF,
По моему никнейму поищи недавнюю тему, которую я создал. В ней  про функторы достаточно много рассказано
ava
mes | 04.01.2013, 23:05 #
Цитата (NoviceF @  4.1.2013,  19:26 findReferencedText)
используя функтор, 

глянуть хотя бы на стл :

using namespace std::placeholders; 
auto found = std::find_if(v.begin(), v.end(),
    std::bind(std::logical_and<bool>()
              , std::bind(std::less<int>(),10, _1)
              , std::bind(std::greater<int>(),15, _1)
            ) );

http://liveworkspace.org/code/409lqK$0

Добавлено позднее:
если вместо 10 и 15 поставить переменные, то реализовать подобное через указатели на функции будет затруднительно..

Добавлено позднее:
ну а с лямбдой, как дальнейшее развитии функтора, выглядит еще удобнее :
http://liveworkspace.org/code/409lqK$2

Добавлено позднее:
ну и с замыканием контекста : http://liveworkspace.org/code/409lqK$3

все это приведенно на скорую руку, на самом деле преимущества гораздо шире )
ava
EvilsInterrupt | 04.01.2013, 23:22 #
mes,
Я бы предпочел так оформить код:

auto found = std::find_if(v.begin(), v.end(),
    [=](int i) 
    {
       return (i>min && i<max); 
    }
);


Понятней как-то... )
ava
NoviceF | 04.01.2013, 23:47 #
Спасибо за примеры, но представление о функторах и лямбдах я имею smile вопрос был больше из области по данному классу.. Вывод, как я понимаю, в том, что в фиксированную сигнатуру, благодаря функторам/лямбдам, можно запихать много чего?
ava
EvilsInterrupt | 04.01.2013, 23:51 #
NoviceF,
Польза от использования функторов в том что это объекты! А объекты имеют состояние. То есть перед подачей объекта куда-либо ты можешь его про инициализировать, а после того как это "куда-либо" отработало ты еще можешь прочитав его состояние знать результат какой-угодно тебе.

Рекомендую книгу Джосьютиса "Стандартная библиотека C++". Сам читаю, очень опечален тем что раньше ее не прочитал (((
ava
NoviceF | 05.01.2013, 00:05 #
Цитата (EvilsInterrupt @  5.1.2013,  00:51 findReferencedText)
Польза от использования функторов в том что это объекты! А объекты имеют состояние. То есть перед подачей объекта куда-либо ты можешь его про инициализировать, а после того как это "куда-либо" отработало ты еще можешь прочитав его состояние знать результат какой-угодно тебе.

спасибо, учту.
Цитата (EvilsInterrupt @  5.1.2013,  00:51 findReferencedText)
Рекомендую книгу Джосьютиса "Стандартная библиотека C++". Сам читаю, очень опечален тем что раньше ее не прочитал ((( 

хороших книжек много и читаю я их не быстро smile но тоже возьму на вооружение.
ava
mes | 05.01.2013, 04:05 #
Цитата (EvilsInterrupt @  4.1.2013,  22:22 findReferencedText)
Я бы предпочел так оформить код:

Цитата (mes @  4.1.2013,  22:05 findReferencedText)
все это приведенно на скорую руку,

 smile 

Цитата (NoviceF @  4.1.2013,  22:47 findReferencedText)
из области по данному классу

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

Цитата (NoviceF @  4.1.2013,  22:47 findReferencedText)
 как я понимаю, в том, что в фиксированную сигнатуру, благодаря функторам/лямбдам, можно запихать много чего?

грубо говоря, да  smile 

Цитата (EvilsInterrupt @  4.1.2013,  22:51 findReferencedText)
 А объекты имеют состояние

 smile 
ava
EvilsInterrupt | 05.01.2013, 10:17 #
NoviceF,
Забыл сказать о "лямбда". Если Вы помните, что в C++03 нужно писать так:


bool ваша_кустом_функция(параметры)
{
   return элементарное действие по установке факта;
}

newIter = std::find_if( beg, end, ваша_кустом_функция );


В случае использования "лямбда", доступных в C++11, когда Вы знаете что ваша_кустом_функция очень маленькая, то Вы можете написать ее код, но без указания ее имени. Это удобно тем что 1) читающий код будет меньше скролить экран в другое место, чтобы увидеть реализацию, т.е. чтение кода будет более эффективным. 2) сокращает набор текста, а программистам же так не нравится много писать
ava
mes | 05.01.2013, 13:55 #
Цитата (EvilsInterrupt @  5.1.2013,  09:17 findReferencedText)
 1) читающий код будет меньше скролить экран в другое место, чтобы увидеть реализацию, т.е. чтение кода будет более эффективным. 

 smile 
Цитата (EvilsInterrupt @  5.1.2013,  09:17 findReferencedText)
2) сокращает набор текста, а программистам же так не нравится много писать

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

и еще кое что забыто, из за того что начали сравнивать лямбду с функцией, позабыв про бинд..
самое важное свойство - это замыкание контекста )) ибо локальную функцию (чтоб не скролить) с небольшой избыточностью, можно получить и без лямбды..
ava
NoviceF | 05.01.2013, 15:44 #

bool ваша_кустом_функция(параметры)
{
   return элементарное действие по установке факта;
}
newIter = std::find_if( beg, end, ваша_кустом_функция );

это же синтаксис и пример использования обычной функции в алгоритме STL?

лямбду я понимаю как нечто

[&m]( int x )
    {
        //do work;   
    }

Это для случая:

for_each( after.begin(), after.end(), [&m]( int x ) {//...});

спасибо Борису (borisbn) :)

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

Цитата (mes @  5.1.2013,  14:55 findReferencedText)
это замыкание контекста 

вот об это не слышал, почитал вику, там как пример замыкания приводятся опять таки лямбда функции. Но в целом смысл понятен. Удобно, что лямбда может обращаться к внешним переменным, в том числе и по ссылке.
ava
mes | 05.01.2013, 17:01 #
Цитата (NoviceF @  5.1.2013,  14:44 findReferencedText)
лямбду я понимаю как нечто 

лямбда на самом деле нечто большее )) если с замыканием она "анологична" функтору, то без контекста - обычной функции..
и ее легко можно использовать для адаптации сигнатуры :


void f(int a, double b)
{
   std::cout << a << " " << b;
}

void call_fn (void(*fn)(double, int), double a, int b )
{
   fn(a, b);
}

int main ()
{
//  call_fn(f, 8.6, 4);

call_fn([](double a, int b){ f(b,a); }, 8.6, 4); 

}


http://liveworkspace.org/code/4xodmZ$0

Добавлено позднее:
то есть, в лямбде сильна не только ее анонимность, а таже ее конвертибельность, при отсутствии (ненужного) оверхеда..

Добавлено позднее:
вот еще один примерчик с лямбдами как с-функции:

typedef void (*pfn)();

pfn arr[] = {
    [](){ std::cout << "H"; }
   ,[](){ std::cout << "a"; }
   ,[](){ std::cout << "l"; }
   ,[](){ std::cout << "l"; }
   ,[](){ std::cout << "o"; }      
};

int main ()
{
   for (auto f : arr) f();

}

http://liveworkspace.org/code/3tkd17$0
ava
NoviceF | 05.01.2013, 17:12 #
Цитата (mes @  5.1.2013,  18:01 findReferencedText)
 


call_fn([](double a, int b){ f(b,a); }, 8.6, 4);

 

головокружительные пируэты  :crazy

Добавлено позднее:
Цитата (mes @  5.1.2013,  18:01 findReferencedText)

  (auto f : arr)

 

просветите, что за синтаксис?

отбой, нашёл в вики.

Надо всётаки когда-нибудь дочитать список "нововведений" в с++11.. где тут смайлик *слоупок*..
ava
mes | 05.01.2013, 17:18 #
Цитата (NoviceF @  5.1.2013,  16:12 findReferencedText)
просветите, что за синтаксис? 

c++11 ranged based for loop 
или тут
ava
NoviceF | 05.01.2013, 17:23 #
Цитата (mes @ 5.1.2013,  18:18)
Цитата (NoviceF @  5.1.2013,  16:12 \\"findReferencedText\\")
просветите, что за синтаксис? 


c++11 ranged based for loop 

или тут

Отличная вещь.. осталось только подружить qnx Momentics с gcc 4.7.. smile 
ava
Нитонисе | 09.01.2013, 10:58 #
В развитие темы smile
Я тут попробовал набросать пример по наводке mes. Пишу в RAD Studio Builder XE, но думаю для данной задачи это не принципиально, просто код билдеровский.
Вот класс

class TTestClass
{
    public:
        void SetFn(void (*fn)())
        {
            this->fn = fn;
        }
        void DoFn()
        {
            fn();
        }
    private:
        void (*fn)();
};

Вот заголовочный файл формы в билдере

class TForm1 : public TForm
{
__published:    // IDE-managed Components
    TButton *Button1;
    TLabel *Label1;
    TRadioButton *RadioButton1;
    TRadioButton *RadioButton2;
    void __fastcall Button1Click(TObject *Sender);
private:    // User declarations

public:        // User declarations
    __fastcall TForm1(TComponent* Owner);
    void Fn1()
    {
        Label1->Caption = "Выполнена функция Fn1";
    };
    void Fn2()
    {
        Label1->Caption = "Выполнена функция Fn2";
    };
};

Вот применение моего тестового класса

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    TTestClass *Test = new TTestClass;
    if (RadioButton1->Checked)
    {
        Test->SetFn(Fn1());
    }
    else
    {
        Test->SetFn(Fn2());
    }
    Test->DoFn();
    delete Test;
}

Ошибка компилятора

[BCC32 Error] Unit1.cpp(23): E2468 Value of type void is not allowed
[BCC32 Error] Unit1.cpp(23): E2342 Type mismatch in parameter 'fn' (wanted 'void (*)()', got 'void')

Это чего он от меня хочет? smile И рабочий ли данный механизм впринципе, когда я своему классу подсовываю функции другого класса в качестве "приемных детей" smile
ava
bsa | 09.01.2013, 11:33 #
Нитонисе, во-первых, указатель на метод класса несколько иначе записывается.
во-вторых, адрес метода класса берется тоже несколько иначе
в-третьих, а какое отношение данный вопрос имеет к обсуждаемой теме? С указателем на функцию в качестве члена класса у тебя проблем нет.
ava
Нитонисе | 09.01.2013, 12:26 #
Цитата (bsa @  9.1.2013,  11:33 findReferencedText)
в-третьих, а какое отношение данный вопрос имеет к обсуждаемой теме? С указателем на функцию в качестве члена класса у тебя проблем нет.

Такое отношение, что я пока не разобрался как это сделать. Мне нужно в свой класс в разные моменты времени подсовывать разные функции других классов. Вот как в этом простом примере я передаю указатели на функции класса формы.
ava
NoviceF | 09.01.2013, 12:54 #
Цитата (Нитонисе @  9.1.2013,  13:26 findReferencedText)
Мне нужно в свой класс в разные моменты времени подсовывать разные функции других классов.

 smile по-моему, какой-то порочный путь.. Какие действия будут выполнять эти функции? Будут ли обращаться к каким-либо приватным членам своего класса?
ava
Нитонисе | 09.01.2013, 13:17 #
Цитата (NoviceF @  9.1.2013,  12:54 findReferencedText)
по-моему, какой-то порочный путь.. Какие действия будут выполнять эти функции? Будут ли обращаться к каким-либо приватным членам своего класса?

Да, будут. Я делаю класс контролирующий открытие/закрытие/сохранение файла. На форме есть набор компонентов, их состояние можно сохранить в файл, либо загрузить из файла. У разных программ будет разный набор этих оконных компонентов, куда будут загружаться данные. Соответственно разная будет реализация функций Load и Save. Но они будут обращаться к данным класса формы.
ava
bsa | 09.01.2013, 13:21 #
Нитонисе, в стандарте С++ указатели на методы класса очень специфичные (я бы сказал - практически бесполезные субстанции). В Борландовской реализации есть более грамотные указатели (несут в себе не только указатель на сам код, но и на объект класса). Для их использования необходимо применять ключевое слово __closure. Поищи документацию во встроенной справке. Там все есть.
Если же тебе хочется работать со стандартными указателями на метод, то делается это так:
class MyClass{
public:
   int f(int) const { return 1; }
};

int main() {
   int (MyClass::*fp)(int) const;
   fp = &MyClass::f;
   ...
   MyClass x;
   ...
   x.(*fp)(10); //вызовет x.f(10)
}


Добавлено позднее:
Цитата (Нитонисе @  9.1.2013,  14:17 findReferencedText)
У разных программ будет разный набор этих оконных компонентов, куда будут загружаться данные. Соответственно разная будет реализация функций Load и Save.
Ох... Если у тебя разные программы, то с разной реализацией нет проблем.
А вот если программа одна, а "формы разные", то да, возможно ты мыслишь в правильном направлении. Но опять же. Не совсем. В Билдере есть уже для этого все что нужно. назначь на onClick вызов нужного метода нужного класса и все.
ava
Нитонисе | 09.01.2013, 13:41 #
bsa

fp = &Simple::f; // что такое Simple в этой строчке?


Цитата (bsa @  9.1.2013,  13:21 findReferencedText)
В Билдере есть уже для этого все что нужно. назначь на onClick вызов нужного метода нужного класса и все.

Я заметил, что у меня от программы к программе копируется код по работе с файлом практически один в один, за исключением функций Load и Save. Поэтому и родилась идея все это привести к единому знаменателю. А какой OnClick предлагается обрабатывать - я не понял. Если имеется ввиду нажатие на кнопку открывающую/закрывающую файл - то конечно это сделать можно, но работа с файлом заключается не только в открытии и сохранении. Там еще контролируется состояние изменения, проверка корректности данных при открытии, при сохранении, вобщем есть ряд действий, которые одинаковы из программы в программу.
ava
mes | 09.01.2013, 13:52 #
Нитонисе, посмотрите в доке (вот опять очередная отсылка smile ) чем является OnClick. Это и будет "указатель на функцию" в терминах билдера.. Их можно использовать не только у форм, а также и у невизуальных компонентов..
ava
bsa | 09.01.2013, 13:54 #
Цитата (Нитонисе @  9.1.2013,  14:41 findReferencedText)
что такое Simple в этой строчке?
исправил

Цитата (Нитонисе @  9.1.2013,  14:41 findReferencedText)
Я заметил, что у меня от программы к программе копируется код по работе с файлом практически один в один, за исключением функций Load и Save.
Сделай абстрактный класс, у которого функции load и save чисто виртуальные. А в каждой своей программе делай наследника, который переопределяет эти функции. Для этого наследование и существует.
ava
Нитонисе | 09.01.2013, 18:26 #
Цитата (bsa @  9.1.2013,  13:54 findReferencedText)
Сделай абстрактный класс, у которого функции load и save чисто виртуальные. А в каждой своей программе делай наследника, который переопределяет эти функции.

Хм. Может это и хорошая идея в данном случае.
ava
bsa | 09.01.2013, 19:56 #
Нитонисе, использование указателей на методы - это обычно всегда плохая идея. Чаще всего, они используются для реализации каких-нибудь "улучшайзеров" языка. Например, boost::bind (или std::bind), boost::function (std::function) и пр. В обычной жизни в них потребность крайне низка. И если вдруг тебе пришла в голову идея их использовать, подумай еще разик, может лучше подойдет наследование или шаблоны?
ava
mes | 09.01.2013, 20:45 #
Цитата (bsa @  9.1.2013,  18:56 findReferencedText)
 В обычной жизни в них потребность крайне низка. 

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

Добавлено позднее:
Цитата (Нитонисе @  9.1.2013,  17:26 findReferencedText)
Хм. Может это и хорошая идея в данном случае. 

 smile 
Зарегистрируйтесь или войдите, чтобы написать.
Фирма дня
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Участники
advanced
Отправить