Russian Qt Forum

Программирование => С/C++ => Тема начата: Fregloin от Декабрь 26, 2014, 16:14



Название: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 26, 2014, 16:14
Привет, кто то сталкивался с делегатами в с++ и как их использовать в наследуемых классах?
Нашёл пока только описание толковое для делегатов одного класса. С наследованием проблемы...

По сути что надо.

Есть класс базовый
Код:
class Base {
   typedef void (Base::*delegate)(SomeClass1 * obj1, SomeClass2 * obj2); //сигнатура делегата
   QHash<QString,delegate>  fdelegateHash;
protected:
  void registerDelegate(const QString & delegateId, delegate delegatePtr);
public:
void   executeDelegate(const QString & delegateName, SomeClass1 * obj, SomeClass2 * obj2);
};

описана сигнатура делегата, а также хеш в котором они будут храниться и вызываться по имени.
Сами делегаты должны быть реализованы в наследуемых классах.

В этом и проблема, что с данной сигнатурой я могу регистрировать делеагты созданные в базовом классе, а с наследником уже не получается.
Либо через reinpret_cast<BaseClass::delegate>. Тогда вызываемый делегат наследника видит только ту часть объекта, которая соответствует классу Base.

Знаю что есть подобній функционал в boost, но я не хочу его за собой тянуть (ради одной функции тянуть все зависимости).


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 26, 2014, 18:12
Ладно, не постесняюсь спросить :)  А разве "делегат" какой-то стандартный термин в плюсах? (никогда не слышал о таком). По тексту я вижу просто указатель на ф-цию член. Если в базовом классе она не виртуальна - получите именно то что описываете


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: _Bers от Декабрь 26, 2014, 20:26
Ладно, не постесняюсь спросить :)  А разве "делегат" какой-то стандартный термин в плюсах?

Да.

см std::function


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: m_ax от Декабрь 26, 2014, 22:04
Если я правильно понял, чего хочет ТС, то придётся шагнуть чуть дальше, чем просто std::function)


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 27, 2014, 09:45
Да.

см std::function
Смотрю http://www.cplusplus.com/reference/functional/function/ (http://www.cplusplus.com/reference/functional/function/), слова "delegate" там нет. Ладно, не суть "как называется", поясните какой смысл вкладывается здесь в этот термин

Спасибо


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Johnik от Декабрь 28, 2014, 00:12
На RSDN есть статья "Указатели на функции-члены и реализация самых быстрых делегатов на С++." (http://www.rsdn.ru/article/cpp/fastdelegate.xml)
Если я не ошибаюсь, там решался этот вопрос:
В этом и проблема, что с данной сигнатурой я могу регистрировать делеагты созданные в базовом классе, а с наследником уже не получается.


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: _Bers от Декабрь 28, 2014, 00:49
поясните какой смысл вкладывается здесь в этот термин

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

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

При помощи делегатов реализуются технологии "системы сообщений", "слоты-сигналы" и тп.

Пример:

http://rextester.com/VASRI32220


Код:
#include <iostream>
#include <functional>
using namespace std;

//предположим у нас есть GUI библиотека
//при помощи которой мы можем создавать кнопки
//однако такие библиотечные классы ничего не знают о классах целевого бизнес-приложения
struct Button
{
    typedef std::function<void()>
        EventClick;
   
    EventClick mEventClick;
   
    //имитируем "клик" на кнопке.
    void SimulationClick()const
    {
        cout<<"GUI: Click!\n";
       
        //когда кнопка впадает в состояние "меня кликнули",
        //она должна испустить сигнал - предупредить о факте клика заинтересованные объекты бизнес-приложения.
       
        //делегат позволяет зделать это, без необходимости знать о точных типах получателей события
        if(mEventClick)
            mEventClick();
    }
   
};


//а этот класс - часть бизнес-модели приложения
//модель никак не связанна с GUI, и ничего не знает о её классах
//ровно, как и GUI ничего не знает о модели
struct Model
{
    //модель реализует поведение на нажатие кнопки.
    //точный тип кнопки при этом не имеет значение.
    //природа источника события не имеет значения.
    void OnClick()const
    {
        cout<<"Model:button was click\n";
    }
};


//бизнес-приложение
int main()
{
    std::cout << "Hello, world!\n";
   
    //здесь мы создаем GUI, и модель.
    //И устанавливаем между ними связь.
   
    //Обратите внимание:
    //изначально, ни модель ни GUI не подозревают о существовании друг друга.
   
    //Для организации взаимодействия между ними используется технология делегатов.
   
    Button button;
    Model model;
   
   
    //заряжаем делегат.
    button.mEventClick = [&model](){ model.OnClick(); };
   
    //теперь, когда на кнопку нажмут, она дернет делегат, который дернет лямбду, которая дернет модель.
   
   
    // есть и другие способы передачи сигнала. Например:
    button.mEventClick = std::bind(&Model::OnClick, &model);
   
    //используя шаблонную магию, бинд генерирует функтор, по своему действию аналогичный лямбде.
    //суть проста: std::function это просто тонкая обертка над функторами,
    //которые дергают функции или функции-члены получателей сообщения
   
    //теперь имитирует нажатие на кнопку:
   
    button.SimulationClick();
}


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: _Bers от Декабрь 28, 2014, 00:56
Если я правильно понял, чего хочет ТС, то придётся шагнуть чуть дальше, чем просто std::function)

Если я правильно понял ТС, то он изобретает именно std::function.

Он не хочет цеплять boost::function, потому что не хочет тащить буст.
Однако, видимо он просто не в курсе, что boost::function официально узаконили.
И он вошел в стандарт с++11

Теперь он доступен, как std::function.



Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 28, 2014, 13:39
Код:
    //заряжаем делегат. 
    button.mEventClick = [&model](){ model.OnClick(); };
   
    //теперь, когда на кнопку нажмут, она дернет делегат, который дернет лямбду, которая дернет модель.
   
   
    // есть и другие способы передачи сигнала. Например:
    button.mEventClick = std::bind(&Model::OnClick, &model);
}
Т.е. в обоих случаях адрес model хранится в структуре function которая умеет вызывать и ф-цию-член?


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: _Bers от Декабрь 28, 2014, 15:41
Т.е. в обоих случаях адрес model хранится в структуре function которая умеет вызывать и ф-цию-член?

std::function - тонкая обертка над функторами. Она хранит объект-функтор по значению.

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

В общем, вы можете скормить std::function любой функтор, какой захотите.
Главное - что бы его operator() отвечал сигнатуре функции.

Пример:
http://rextester.com/DKA83721

Код:
#include <iostream>
#include <functional>
using namespace std;


struct CustomFunctor
{
    bool operator()(int v)const { return v%2 == 0; }
};

int main()
{
    std::cout << "Hello, world!\n";
    
    
    //умеет запускать любые функции/функции-члены
    //которые возвращают тип bool
    //и принимают тип int
    typedef std::function<bool(int)>
        Executer;
    
    
    //инициализируем делегат кастомным функтором
    
    //ПРИМЕЧАНИЕ: operator() функтора
    //должен соотвествовать типу функции bool(int)
    //проверка типов выполняется времени компиляции
    
    Executer ex = CustomFunctor();
    
    
    //теперь можно запускать
    const bool result1 = ex(10);
    const bool result2 = ex(11);
    
    if(result1)
        cout<<"ex(10) is true\n";
    else
        cout<<"ex(10) is false\n";
    
    if(result2)
        cout<<"ex(11) is true\n";
    else
        cout<<"ex(11) is false\n";
}

Но обычно никто кастомные функторы не делает.

В основном используют std::bind,
который при помощи шаблонной магии генерирует функтор заточенный под вызов для конкретного объекта
При этом объект можно сохранять по ссылке/значению/смарт-поинтер  (насчет смарта не уверен).

Ну лямбда - это понятно, это функтор который будет делать то, что вы сами там пропишите.



Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 29, 2014, 11:48
В основном используют std::bind,
который при помощи шаблонной магии генерирует функтор заточенный под вызов для конкретного объекта
Да, с bind хорошо. А вот этот сам "объект-функтор" - он "физически" существует, или это темплейт-подстава? (наверное подстава). Напр нет С++ 11, как самостоятельно получить тот же ф-ционал? (просто с целью уяснить)

Спасибо


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Johnik от Декабрь 29, 2014, 12:12
Напр нет С++ 11, как самостоятельно получить тот же ф-ционал?
повторюсь:
На RSDN есть статья "Указатели на функции-члены и реализация самых быстрых делегатов на С++." (http://www.rsdn.ru/article/cpp/fastdelegate.xml)


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 29, 2014, 12:26
повторюсь:
На RSDN есть статья "Указатели на функции-члены и реализация самых быстрых делегатов на С++." (http://www.rsdn.ru/article/cpp/fastdelegate.xml)
И что? Все должны читать много страниц довольно субъективного текста, еще и почти десятилетней давности?  :)


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Johnik от Декабрь 29, 2014, 12:35
И что? Все должны читать много страниц довольно субъективного текста, еще и почти десятилетней давности?  :)
А как вы хотели что-то узнать не читая?
Почитайте исходники того же std::function, общее представление может дать, не смотря на наличие С++11, но опять же там тоже много текста.


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 29, 2014, 12:39
печально, но в моей версии 5.3.2 std::function не нашлось :(


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 29, 2014, 13:00
Ладно упрощу вопрос:

Есть базовый класс
Код:
class CBaseClass {

 //приватные данные для этого класса
 int private_base_data;

 {virtual?} void click(ClassA * param)  = 0;//сигнатура обработчика

 QHash<QString,click> hash;

 void registerClick(QString name, click func) { hash[name]=func; }

 void callClick(QString name, ClassA * objectA) {
 click func = hash.value(name);
 if(func)
 func(objectA);
 }

};

Есть наследованный класс
Код:
class CDerivedClass : public CBaseClass 
{
 int private_derived_data;

 void click1(ClassA * param);
 void click2(ClassA * param);
 ...
 void clickN(ClassA * param);
};

class CDerivedClass2 : public CDerivedClass
{
 int private_derived2_data;
 void click2_1(ClassA * param);
 ...
 void click2_N(ClassA * param);
};

Что я хочу получить: что бы можно было регистрировать в хеше функции обработчики, и потом их вызывать по имени и эти функции видели данные всех классов от базового до текущего.
К сожалению сейчас это все реализовано через статические функции-члены что не совсем удобно. По сути это замена switch оператора, только параметр строковый. т.е. функции обработчику соотвествует определенное название в виде строки, и когда она приходит, нужно вызвать соотвествующий член-функцию, передать ей параметр и что бы функция вызвалась в том классе, в котором она описана а не в базовом (когда все private_derived..data не видны).


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Johnik от Декабрь 29, 2014, 13:06
печально, но в моей версии 5.3.2 std::function не нашлось :(
std::function, это не Qt, это стандартные либы C++

Если у вас gcc добавьте опцию компилятора: -std=c++11
Если msvc то версия должна быть не меньше 2010 (в опциях ничего не надо добавлять).


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 29, 2014, 13:10
в Qt редакторе при компиляции ругается и подчеркивает как ненайденный элемент.


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 29, 2014, 13:23
Что я хочу получить: что бы можно было регистрировать в хеше функции обработчики, и потом их вызывать по имени и эти функции видели данные всех классов от базового до текущего.
По-моему не хватает только virtual.

А как вы хотели что-то узнать не читая?
Почитайте исходники того же std::function, общее представление может дать, не смотря на наличие С++11, но опять же там тоже много текста.
Пример: указатель на ф-цию член. Тут все ясно - хранится адрес ф-ции (чуть по-другому для virtual). А что в случае std::function? Тоже хранится адрес ф-ции или только объекта или как? Вот Вы много читали, объясните please


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Johnik от Декабрь 29, 2014, 13:29
в Qt редакторе при компиляции ругается и подчеркивает как ненайденный элемент.
Какой компилятор?

Пример: указатель на ф-цию член. Тут все ясно - хранится адрес ф-ции (чуть по-другому для virtual). А что в случае std::function? Тоже хранится адрес ф-ции или только объекта или как?
тут описание и хорошие примеры:
http://ru.cppreference.com/w/cpp/utility/functional/function


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 29, 2014, 13:31
вот такой


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 29, 2014, 13:34
ага, прописал в mkspecs/win32-g++

QMAKE_CFLAGS = -std=c++11

собралось


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Johnik от Декабрь 29, 2014, 13:35
вот такой
1. я не уверен, но возможно там кавычки не нужны
2. выполнить qmake

можно в pro файл добавить строку:
Код:
gcc:QMAKE_CXXFLAGS += -std=c++11
и выполнить qmake


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 29, 2014, 13:43
тут описание и хорошие примеры:
http://ru.cppreference.com/w/cpp/utility/functional/function
Ну то я читал не раз, правда в чуть др редакции http://www.cplusplus.com/reference/functional/function/ (http://www.cplusplus.com/reference/functional/function/). Да. хорошее описание и примеры, вообще - прекрасный справочник. Но это "как юзать" - а я хотел узнать о внутреннем механизме. Ладно, "понял, отстал"  :)

ага, прописал в mkspecs/win32-g++
Наверное можно и std::function, но и обычный указатель на ф-цию член (без С++ 11) должен работать


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 29, 2014, 13:47
обычный указатель на функцию-член работает только в классе, в котором он описан. В наследниках уже такое не прокатит, проверено.


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 29, 2014, 15:14
никак не могу разобраться с std::bind, std::function.
Нашёл простые примеры, но толку с них для меня не много.

Вот что я пытаюсь сделать
Код:
class QRailItem; //графический элемент на сцене

class CBaseController {
   typedef  std::function<void(QRailItem*)> ClickHandlerFunc;
   QHash<QString,ClickHandlerFunc>                   fclickHahdlers;
protected:
   void    setClickHandlerForButton(const QString & role, ClickHandlerFunc handler);
private slots:
   void    handleViewItemClick();      //обработка нажатия на граф.объект
};

void CBaseController ::setClickHandlerForButton(const QString &role, CRailController::ClickHandlerFunc handler)
{
    fclickHahdlers[role] = handler;
}

void CRailController::handleViewItemClick()
{
    QRailItem       *   railItemView = qobject_cast<QRailItem*>(sender());  //получаем отправителя
    if(railItemView && railItemView->controllerLink())                      //если есть
    {
        try
        {
            ClickHandlerFunc func = fclickHahdlers.value(railItemView->referenceRole());
            if(func)
                func(railItemView);
        }
        catch(std::bad_function_call call)
        {
            qDebug("bad function call %s",call.what());
        }
    }
}

Теперь я объявляю наследника
Код:
class CDerivedController : public CBaseController
{
    void    sendCommandI(QRailItem *eventSource);
    void    sendCommandOI(QRailItem *eventSource);
    void    sendCommandZP(QRailItem *eventSource);
}

CDerivedController::CDerivedController():CBaseController()
{
    ClickHandlerFunc    f = std::bind(&CRailCrossController::sendCommandI);//ругатется здесь
    setClickHandlerForButton(iBtnRole,f);
}

Подскажите как правильно мне все сделать...


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 29, 2014, 15:50
обычный указатель на функцию-член работает только в классе, в котором он описан. В наследниках уже такое не прокатит, проверено.
Не раз использовал, работает. И должен работать - иначе зачем бы этот указатель имел поддержку вызова виртуалов (а он ее имеет)


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 29, 2014, 16:47
Покажите пример, потому что я пробовал делать как вы говорите, не работает


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 29, 2014, 17:28
Покажите пример, потому что я пробовал делать как вы говорите, не работает
Код
C++ (Qt)
#include <QtCore>
#include <QDebug>
 
struct Base {
typedef void (Base::*MemberFunc)( int );
 
Base( void )
{
mHash["DoPrn"] = &Base::DoPrn;
}
 
virtual void DoPrn( int val )
{
qDebug() << "Base DoPrn" << val;
}
 
void CallByName( const QString & name, int val )
{
MemberFunc func = mHash.value(name);
if (func) (this->*func)(val);
}
 
QHash<QString, MemberFunc> mHash;
};
 
struct Derived : public Base {
virtual void DoPrn( int val )
{
qDebug() << "Derived DoPrn" << val;
}
};
 
int main(int argc, char *argv[])
{
(void) argc;
(void) argv;
 
Derived test;
test.CallByName("DoPrn", 5);
 
   return 0;
}
 


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: m_ax от Декабрь 29, 2014, 20:43
Выше всё уже показали же..  Это делается с помощью std::function + std::bind

Код
C++ (Qt)
using namespace std::placeholders;
 
class base
{
public:
   template <class T, class M>
   void register_delegate(const std::string & name, T *obj, M delegate)
   {
       _map[name] = std::bind(delegate, obj, _1, _2);
   }
 
   void run(const std::string & name, int a, int b)
   {
       _map[name](a, b);
   }
 
private:
   std::map<std::string, std::function<void(int, int)>> _map;
};
 
class derived : public base
{
public:
   void delegate(int a, int b)
   {
       std::cout << "derived::delegate(int a, int b); a + b = " << a + b << std::endl;
   }
 
   void delegate2(int a, int b)
   {
       std::cout << "derived::delegate2(int a, int b); a * b = " << a * b << std::endl;
   }
};
 
 
 
   derived d;
   d.register_delegate("id1", &d, &derived::delegate);
   d.register_delegate("id2", &d, &derived::delegate2);
 
   d.run("id1", 1, 2);
   d.run("id2", 1, 2);
 


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: _Bers от Декабрь 29, 2014, 21:04
Да, с bind хорошо. А вот этот сам "объект-функтор" - он "физически" существует, или это темплейт-подстава? (наверное подстава). Напр нет С++ 11, как самостоятельно получить тот же ф-ционал? (просто с целью уяснить)

Делегаты наподобие std::function можно завелосипедить самостоятельно.
Это не сложно.

Некоторое время назад, я изготавливал собственные велосипеды для с++03.

Только у меня использовалась связка: динамика и статика.

Статика работает по такому же принципу, что и std::function.
Скорость вызова - такая же.
Скорость бинда на порядок (в десять раз) быстрее, чем у стандартного аналога.

Динамика работает на два порядка медленнее (в 100 раз дольше).


Дизайн такой:

Код:
TConnector<R(int)> tcon(obj, &Some::Method);

//--- обратите внимание: динамический делегат не является продуктом шаблона
Connector con(obj, &Some::Method);

Суть динамического делегата в том, что не являясь продуктом шаблона, такие делегаты можно хранить в одном массиве, при этом нацеливать их можно куда угодно.

Это делает динамический делегат очень удобным и быстрым решением, когда в статике ещё не известно на что придется нацеливаться делегату.

Если вам это интересно, то мы можем подробно разобрать рецепт изготовления в рамка с++03.


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Fregloin от Декабрь 30, 2014, 12:59
Спасибо m_ax за доходчивый пример. Все хорошо кроме того что используются шаблоны, т.к. базовый класс у меня находится в отдельной библиотеке DLL, но вписав метод register_delegate в тело класса все собралось и заработало! Спасибо всем за пояснения, потому что честно говоря я с шаблонами не совсем дружу и редко их использую, а тут еще мудреные function bind ...  :-\


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: Igors от Декабрь 30, 2014, 13:40
//--- обратите внимание: динамический делегат не является продуктом шаблона

Если вам это интересно, то мы можем подробно разобрать рецепт изготовления в рамка с++03.
Да, интересно (не представляю как/куда Вы спрячете тип). А без (долбаных) шаблонов - вообще прекрасно!

Спасибо


Название: Re: Делегаты С++ в классах-наследниках
Отправлено: m_ax от Декабрь 30, 2014, 21:32
Цитировать
Да, интересно (не представляю как/куда Вы спрячете тип).
А про полиморфизм не слышали?

Цитировать
А без (долбаных) шаблонов - вообще прекрасно!
И (долбаные) шаблоны там всё равно будут.. И откуда такие комплексы по поводу них?)