Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.88/32: Рейтинг темы: голосов - 32, средняя оценка - 4.88
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
1

Использование указателя на метод вместо виртуального метода

20.11.2010, 12:33. Показов 5982. Ответов 45
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Имеется базовый класс Base. Имеется производный от Base класс Derived. В классе Derived требуется выполнить некоторое действие, которое практически полностью эквивалентно для любого производного от Base класса, за исключением небольшого фрагмента.

Схематично код выглядит так:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Base
{
  void Exec (void)
  {
    // общие действия
    ...
 
    // конкретные действия для производных классов
    Tail ();
  }
 
  virtual void Tail (void) = 0;
};
 
class Derived : public Base
{
  void MyExec (void)
  {
    // В процессе исполнения будет исполнен виртуальный метод Tail
    Exec ();
  }
 
  virtual void Tail (void)
  {
    // Определяем конкретные действия для нашего класса
  }
}
Однако при такой схеме возможен только один вид частных действия для производного класса. А хотелось бы, чтобы внутри производного класса можно было выполнять разные действия. Для этого логично было бы использовать указатель на метод. Т.е. что-то типа такого

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Base
{
  void Exec (void (Base::*tail)(void))
  {
    // общие действия
    ...
 
    // конкретные действия для производных классов
    // здесь уже через указатель  на метод
    (this->*tail) ();
  }
};
 
class Derived : public Base
{
  void MyExec (void)
  {
    // Выполняем основные действия с двумя разными типами
    // частных действий. Необходимую операцию преобразования
    // указателя на метод опускаю, чтобы глаза не резало
    Exec (Tail1);
    Exec (Tail2);
  }
 
  void Tail1 (void)
  {
    // Определяем конкретные действия "вариант1" для нашего класса
  }
 
  void Tail2 (void)
  {
    // Определяем конкретные действия "вариант2" для нашего класса
  }
}
Собственно вопрос: насколько опасным является преобразование указателя на метод в данном случае. Опасность не столько в преобразовании указателя на метод, сколько в том, что при вызове Derived-метода через указатель на класс Base может прийти кривой this. Понятно, что в случае "простого" наследования ничего страшного нет. Будет ли что-то криво работать в случае множественных или виртуальных наследований или в каких-то ещё тяжёлых случаев. Или такой способ при некоторых ограничениях на тип наследования можно считать надёжным и корректным? Можно ли как-то сделать статический контроль в случаях, когда такой вариант оказывается некорректным?
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
20.11.2010, 12:33
Ответы с готовыми решениями:

Вызов виртуального метода базового класса из указателя производного
Допустим есть такой код: #include <iostream> class Base { public: virtual void f() {...

Почему при переопределении виртуального метода в производном классе выводится метод базового?
Всем добра! Помогите разобраться почему при переопределении виртуального метода в производном...

Использовать метод transform() вместо метода sort()
Добрый день , надо исправить код , заменив метод sort() , методом transform(), не могу уловить...

Как сделать функцию от указателя на класс и указателя на метод?
Не получается сделать функцию, параметрами которой являются указатель на класс и на метод....

45
270 / 176 / 46
Регистрация: 12.03.2010
Сообщений: 494
20.11.2010, 20:23 2
Вообще решение через указатели на функции-члены выглядит устрашающе немного. А как это все будет работать зависит уже от компилятора. Можно уточнить задачу, зачем это понадобилось и почему, например, не устраивает чисто виртуальная функция с дефолтным поведением?

C++
1
2
3
4
virtual void Tail (void) = 0
{
    // Базовые операции для всех классов
}
А в наследуемом классе просто вызывать дефолтную реализацию.

Да и само построение логики выглядит немного странно
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
21.11.2010, 00:02  [ТС] 3
Цитата Сообщение от Manjak Посмотреть сообщение
почему, например, не устраивает чисто виртуальная функция с дефолтным поведением?
Потому что дефолтное не всё действие, а только часть. Для чего это нужно. Базовый класс - это некое удобное средство для реализации запуска кодов в потоке. Чтобы весь геморрой по созданию-удалению потока и синхронизации был в базовом классе, а в производных классах только задавалось действие. Tail - это "полезное" действие, которое выполняет производный класс в потоке. Exec - это "геморройное" действие по управлению потоком
0
270 / 176 / 46
Регистрация: 12.03.2010
Сообщений: 494
21.11.2010, 00:59 4
Наследование тут, по моему, не лучшее решение. Я буквально месяц назад писал подобную штуку, шаблоны для такого дела - самое оно. Взять тот же function или async для примера. Если нужно выполнить несколько действий, то элементарно организовывается очередь указателей на функции для выполнения, синхронизацию тоже можно запихнуть в исполняющий поток.
1
274 / 175 / 12
Регистрация: 14.03.2010
Сообщений: 501
21.11.2010, 01:00 5
Может, я не до конца понимаю задачу, но почему бы просто не вынести исполнение функции "Tail" из функции "Exec" базового класса в "MyExec" наследованного?
1
84 / 57 / 8
Регистрация: 07.08.2010
Сообщений: 185
21.11.2010, 09:23 6
Как вариант можешь передавать в Exec функтор:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
template <typename T, typename MemFun>
struct memfun_binder : std::unary_function<void, void>
{
    memfun_binder(T * obj, MemFun memfun)
        : obj_(obj)
        , memfun_(memfun)
    {}
 
    void operator()()
    {
        memfun_(obj_);
    }
 
    T      * obj_;
    MemFun & memfun_;
};
 
 
class Base {
    // ...
    template <typename Func>
    void Exec(Func tail_func) {
        // ...
        tail_func();
     }
};
 
template <typename T, typename MemFun>
memfun_binder<T, MemFun> memfun_bind(T * obj, MemFun memfun)
{
    return memfun_binder<T, MemFun>(obj, memfun);
}
 
class Derived : public Base {
    // ...
private:
    void tail1() { ... }
    void tail2() { ... }
 
public:
    void MyExec() {
        Exec(memfun_bind(this, std::mem_fun(&Derived::tail1)));
        Exec(memfun_bind(this, std::mem_fun(&Derived::tail2)));
    }
};
1
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
21.11.2010, 10:15  [ТС] 7
Цитата Сообщение от Manjak Посмотреть сообщение
Наследование тут, по моему, не лучшее решение
Наследование приходится строить из-за того, что единственный простой способ, который я нашёл для нормальной передачи сообщения из потока в основной процесс - это через Handle окна. Мне нужно в потоке запустить некое "длительное" действие. Если это действие запускать в основной задаче, то будут тормозить (подвисать) GUI. По завершению исполнения потока я через PostMessage уведомляю окно о том, что данные готовы и надо их нарисовать на GUI. В этом месте используется Handle окна. Я бы хотел эту бодягу сделть универсальной и отрезать от окна, но тема Вопрос по PostMessage так и осталось неотвеченной, так что пока работаю так.

Цитата Сообщение от volovzi Посмотреть сообщение
Может, я не до конца понимаю задачу, но почему бы просто не вынести исполнение функции "Tail" из функции "Exec" базового класса в "MyExec" наследованного?
MyExec наследованного класса исполняется в главном процессе. Далее во время вызова Exec создаётся поток и функция Tail уже исполняется в потоке. Как уже говорилось, весь этот паровоз строится для того, чтобы реализовать простой класс для простого запуска подзадачи в потоке, а всю техническую часть по работе с потоком заключить в этом классе. К тому же хочется сделать класс условно универсальным (т.е. чтобы его можно было без дополнительных телодвижений использовать в другой программе, в том числе и в виде откомпилированной библиотеки)

Цитата Сообщение от alexzak Посмотреть сообщение
Как вариант можешь передавать в Exec функтор:
Я пока плохо знаком с Си++, чтобы с ходу осилить такую конструкцию. Попробую в неё вникнуть. Общий принцип понятен и выглядит логичным: при использовании указателей на функцию не будет преобразования типов, а следовательно не будет преобразования типов при передаче this - именно та опасность, о которой я писал. Скорее всего это именно то, что нужно
0
270 / 176 / 46
Регистрация: 12.03.2010
Сообщений: 494
21.11.2010, 10:41 8
Судя по той теме, тут можно обойтись и чистым С, винда же предоставляет стандартный пул потоков, в который можно просто пихать свои функции для исполнения (я его частенько использовал при написании небольших серверов). А для уведомление потока-обработчика можно использовать те же простенькие ивенты
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
21.11.2010, 10:47  [ТС] 9
Цитата Сообщение от Manjak Посмотреть сообщение
Судя по той теме, тут можно обойтись и чистым С, винда же предоставляет стандартный пул потоков, в который можно просто пихать свои функции для исполнения (я его частенько использовал при написании небольших серверов). А для уведомление потока-обработчика можно использовать те же простенькие ивенты
Для реализации потока я и так обхожусь чистым Си. Но мне нужно сделать класс для окна (формы). Чтобы потом все окна программы наследовать не от TForm, а от моего класса. И лёгким движением руки запускать нужную функциональность в потоке. Т.е. в конечном итоге реализация функциональности потока выполняется на Си++. Если бы была программа на Си без окон - я бы без вопросов работал с указателями на функцию
0
270 / 176 / 46
Регистрация: 12.03.2010
Сообщений: 494
21.11.2010, 11:29 10
QueueUserWorkItem - самый простой вариант( винда сама все запустит ). Ну, а в стиле С++, мне все же видится асинхронный делегат. Как окончательно проснусь - набросаю простой вариант
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
21.11.2010, 13:41  [ТС] 11
Цитата Сообщение от Manjak Посмотреть сообщение
Как окончательно проснусь - набросаю простой вариант
Сейчас у меня всё работает примерно по следующей схеме. Для простоты возьмём окно, которое отображает скачанную из инета картинку. На окне есть поле для картинки и кнопка. Смысл кнопки такой же, как и у браузеров. Если мы ничего не делаем, то кнопка имеет функциональность "Reload" (т.е. инициирует процесс скачивания и отображения картинки). Если идёт процесс скачивания, то кнопка имеет функциональность "Stop" (т.е. прервать текущий процесс скачивания)

Схематично примерно всё выглядит так. Здесь отображаю только код для окна с картинкой (производный класс), потому как базовый класс условно считаем чёрным ящиком, который надо реализовать. Сейчас чёрный ящик реализован через виртуальные методы

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// Обработка события нажатия на кнопку Reload/Stop
void __fastcall
TWindowChart::ButtonClick (TObject *Sender)
{
    if (m_IsReloading)
    {
        // KillExecute - метод базового класса для прибития потока
        // Возвращает true, если поток убили, возвращает
        // false, если убить не получилось (к моменту начала прибития поток
        // сам завершился)
        // В любом случае отработает метод Execute3 (см. ниже).
        // Таким образом в конкретно данном случае результат, возвращённый
        // методом KillExecute нам не важен
        KillExecute();
    } else
    {
        ReloadImage();
    }
}
 
// Скачивание и отображение картинки
void
TWindowChart::ReloadImage (void)
{
    // StartExecute - метод базового класса. В процессе своего исполнения
    // он исполнит три метода (виртуальных): Execute1, Executre2 и Execute3.
    //
    // Execute1 исполнится в самом начале в главном процессе и имеет смысл
    // подготовки к исполнению потока. После исполнения данного метода
    // мы выйдем из StartExecute и главный процесс продолжит работать
    // (обрабатывать события) без тормозов
    //
    // Execute2 исполнится в потоке. Он будет исполняться параллельно
    // основному процессу и в этом методе нужно поместить "длительные"
    // действия (скачивание из инета). Пока этот поток тормозит об инет,
    // основной процесс может нормально реагировать на события (двигать
    // окошки и т.п.
    //
    // Execute3 будет исполнен в главном процессе после завершения потока
    // Вызовется этот метод через стандартную очередь сообщений
    // (т.е. наравне со всеми прочими событиями типа нажатия на кнопку)
    // При этом параметром будет передана причина завершения потока:
    // поток завершился самостоятельно, принудительно (через Kill) или
    // по таймауту 
    //
    // ВАЖНО!
    // __ВО ВРЕМЕНИ__ эти три метода исполняются последовательно.
    // Это позволяет не корячиться с синхронизацией для обращения
    // к полям данного класса (m_IsReloading в нашем примере)
    StartExecute (30);  // параметр - время таймаута
}
 
void
TWindowChart::Execute1 (void)
{
    // Подготовительные действия. Вычисляем URL картинки и т.п.
    ...
 
    // На кнопку наносим надпись "Stop"
    ...
 
    // Флажок, который определяет текущую реакцию на нажатие на кнопку
    m_IsReloading = true;
}
 
void __IN_THREAD__
TWindowChart::Execute2 (void)
{
    // Скачиваем данные из инета
    ...
}
 
void
TWindowChart::Execute3 (int reason)
{
    // Через параметр reason передаётся причина завершения потока
 
    // Если поток прервали принудительно, то отображать ничего не надо
    if (reason == "поток умер через Kill")
    {
        goto Finish;
    }
 
    // Если поток умер по истечении времени (таймауту), то выдадим ошибку
    if (reason == "поток умер по таймауту")
    {
        UserError ("таймаут");
        goto Finish;
    }
 
    // Поток успешно завершился
    // Проверяем ошибки: не удалось скачать данные с сервера, данные скачались,
    // но картинка оказалась битая и т.п. Рисуем картинку в случае успеха
    ...
 
  Finish:
    // Завершающие действия
    ...
 
    // На кнопку наносим надпись "Reload"
    ...
 
    // Флажок, который определяет текущую реакцию на нажатие на кнопку
    m_IsReloading = false;
}
В конечном итоге хотелось бы, чтобы код окна остался таким же простым (весь геморрой по работе с потоком унесён в базовый класс). Только использование указателя на метод вместо виртуального метода сделало бы это дело более гибким. Не говоря о том, что такой код было бы проще понимать.

Нынешний вариант тоже работоспособен, потому как если нужно исполнять различные действия, то по дополнительному параметру можно настраивать действия внутри Execute1, ... Т.е. вопрос упирается в удобство. Вторым бонусом работы с указателями будет то, что проще организовывать работу с несколькими потоками
0
270 / 176 / 46
Регистрация: 12.03.2010
Сообщений: 494
21.11.2010, 15:59 12
Вот самый простенький вариант реализации:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
typedef unsigned (__stdcall *PTHREAD_START)(void *);
 
#define chBEGINTHREADEX(psa, cbStack, pfnStartAddr,         \
                        pvParam, fdwCreate, pdwThreadID)    \
                        ((HANDLE) _beginthreadex(           \
                        (void *) (psa),                     \
                        (unsigned) (cbStack),               \
                        (PTHREAD_START) (pfnStartAddr),     \
                        (void *) (pvParam),                 \
                        (unsigned) (fdwCreate),             \
                        (unsigned *) (pdwThreadID)))
 
template <class ExecRoutine_, class Arg_>
class async_delegate : public unary_function<Arg_, DWORD>
{
public:
 
 
public:
 
                            async_delegate      ( ExecRoutine_ pStartAddr_)
                                : m_pExecFunc   ( pStartAddr_ ),
                                  m_hExecThread ( INVALID_HANDLE_VALUE )
                            {
                            }
 
public:
 
    virtual DWORD           operator()          (Arg_* arg1_)
    {
        DWORD  dwResult    = S_OK;
        HANDLE hExecThread = INVALID_HANDLE_VALUE;
 
        __try
        {
            if (m_hExecThread != INVALID_HANDLE_VALUE)
            {
                __leave;
            }
 
            m_hExecThread = chBEGINTHREADEX(NULL, 
                                            0, 
                                            m_pExecFunc,
                                            arg1_,
                                            0,
                                            NULL);
 
            if (hExecThread == INVALID_HANDLE_VALUE)
            {
                // Handle error
            }
 
        }
        __finally
        {
        }
        
        return dwResult;
    }
 
public:
            DWORD           Pause               ( void )
            {
                DWORD dwResult         = S_OK;
                DWORD dwExitCodeThread = EXIT_SUCCESS;
 
                __try
                {
                    dwExitCodeThread = Status();
 
                    if (dwExitCodeThread == -1)
                    {
                        dwResult = S_FALSE;
                        __leave;
                    }
 
                    if (dwExitCodeThread == STILL_ACTIVE)
                    {
                        dwResult = SuspendThread(m_hExecThread);
 
                        if (dwResult == (DWORD)-1)
                        {
                            dwResult = GetLastError();
                            __leave;
                        }
                    }
                    else
                    {
                        dwResult = dwExitCodeThread;
                        __leave;
                    }
                }
                __finally
                {
 
                }
 
                return dwResult;
            }
 
            DWORD           Resume              ( void )
            {
                DWROD       dwResult       = S_OK;
                DWORD       dwSuspendCount = 0;
                __try
                {
                    if (Status() == INVALID_HANDLE_VALUE)
                    {
                        __leave;
                    }
 
                    dwSuspendCount = ResumeThread(m_hExecThread);
 
                    if (dwSuspendCount == (DWORD)-1)
                    {
                        dwResult = GetLastError();
                        __leave;
                    }
                }
                __finally
                {
                }
                
                return dwResult;
            }
 
            DWORD           Stop                ( void )
            {
                DWORD dwResult         = S_OK;
                DWORD dwExitCodeThread = EXIT_SUCCESS;
                __try
                {
                    dwExitCodeThread = Status();
 
                    if (dwExitCodeThread == -1)
                    {
                        dwResult = S_FALSE;
                        __leave;
                    }
 
                    if (dwExitCodeThread == STILL_ACTIVE)
                    {
                        // Так делать нельзя
                        TerminateThread(m_hExecThread, ERROR_CANCELLED);
                    }
                    
                }
                __finally
                {
                    if (SUCCEEDED(dwResult))
                    {
                        m_hExecThread = INVALID_HANDLE_VALUE;
                    }
                }
 
                return dwResult;
            }
 
            DWORD           Status              ( void )
            {
                DWORD dwExitcodeThread = -1;
 
                __try
                {
                    if (m_hExecThread == INVALID_HANDLE_VALUE ||
                        !GetExitCodeThread(m_hExecThread, &dwExitcodeThread))
                    {
                        __leave;
                    }
                }
                __finally
                {
                }
 
                return dwExitcodeThread;
            }
private:
    ExecRoutine_        m_pExecFunc;
    HANDLE              m_hExecThread;
};
И не надо никакого наследования, хотя тема и не относится к проэктированию, наследование нужно применять только когда обьект А действительно Является обьектом Б или замещает виртуальные функции, в данном же случае мелкий обьект просто выполняет совершенно отделенные от окна функции - можно просто обойтись прайвет членом

Добавлено через 12 минут
В запуске потока код поправиль забыл
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
21.11.2010, 16:20  [ТС] 13
В твоём коде нет самого главного - вызов события, когда поток завершил работу. В ссылке из 7-го поста я именно для того и поднял тему. По завершении работы потока мне нужно внутри класса окна обработать событие завершения. Неважно каким способом, важно лишь, чтобы это исполнялось в главном процессе. Это гарантирует мне то, что в этот момент мне не придёт никакое другое событие, а потому и не потребуется медитировать с синхронизацией

Добавлено через 13 минут
Собственно, callback по завершении работы потока - это единственное, с чем я не могу нормально разобраться (а точнее, я понял, как это делать в контексте окна, но не понял, как это делать "в воздухе"). Так же хочется, чтобы callback'ом был метод класса, а не просто функция.

Использование функции для callback'а действий в потоке - это механизм, на котором опирается низкоуровневое (на уровне ОС) создание потоков. И этот механизм хорошо отображается на язык Си, потому как в Си функция она и в африке функция. Чего не скажешь о Си++, ибо метод - он требует косвенного наличия this'а, который в общем случае невозможно в этот механизм включить, потому что при преобразовании this'а к void* (или любому типу, отрезанному от класса) теряется возможность вызвать метод класса. В то время как на Си любые данные можно транзитно передавать через ОС в виде void*.

То, чего я хочу - это реализовать УДОБНУЮ В ИСПОЛЬЗОВАНИИ обёртку для классов. И даже что-то более узкое и конкретное: обёртку для работа в классе TForm. Конечно, в качестве callback'а можно использовать и статический метод, но такой способ влечёт за собой дополнительные телодвижения, которые в каждом конкретном случае выливаются в постоянный копипаст. Я в очередной раз специально в первом посте ставил вопрос максимально абстрактно, потому что давно знаю, что при конкретной постановке сложного вопроса в 9 случаях из 10 я услышу ответ не на свой вопрос, а на то, что мне в данный момент не интересно. Точно так же пост #12 является ответом на вопрос, который я не задавал. Хотя, несомненно, этот код является полезным материалом. Но не в контексте данного вопроса

Что касается сути вопроса, то ответ я уже получил - использовать template'ы. Только надо будет это дело переварить
0
270 / 176 / 46
Регистрация: 12.03.2010
Сообщений: 494
21.11.2010, 16:36 14
На возможностях старого стандарта это сделать достаточно трудно, чтобы любую функцию запустить в отдельном потоке, а возможности нового стандарта пока не трогал, потому как не все еще используют поддерживающие их компиляторы. Из готовых решений есть Proactor
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
21.11.2010, 16:53  [ТС] 15
Цитата Сообщение от Manjak Посмотреть сообщение
На возможностях старого стандарта это сделать достаточно трудно, чтобы любую функцию запустить в отдельном потоке
Если внутри класса ручками создавать поток, в качестве Callback'а назначить статический метод, в качестве параметра при создании потока передать this, то всё нормально делается. За исключением того, что в каждом случае создания потока придётся повторять этот геморрой (копипаст). Мне хотелось это дело вытащить в отдельный класс, и при этом столкнулся с проблемой того, что при работе с указателями на метод теряется тип this'а. А до template'ов сам как-то не смог додуматься (потому что ни разу с ними не работал)

Цитата Сообщение от Manjak Посмотреть сообщение
Из готовых решений есть Proactor
На мой взгляд любой начинающий должен на практике пройти этап конструирования велосипедов. Только так можно научиться делать правильно и не делать неправильно. А потом можно уже брать и готовые реализации, потому что чётко будешь понимать, что там делается, а не тупо срисовывать чужой код. Так что пока я поработаю с велосипедами
0
270 / 176 / 46
Регистрация: 12.03.2010
Сообщений: 494
21.11.2010, 17:00 16
В каждом конкретном случае описывать такой себе ThreadDummy действительно просто, но сделать универсальный обьект для разнотипных функций с переменным числом параметров - тяжко на старых возможностях. Тот шаблон что я выставил можно немного переделать и сделать просто внутреннюю функцию потока, внутри которой будет вызываться переданная ей функция и колбек в конце.

C++
1
2
3
4
5
6
7
8
9
10
11
static unsigned __stdcall ThreadDummy ( void * arg_) // аргументом будет кортеж данных- аргументов функции
{
    __try
    {
        StartAddr(...);
    }
    __finally
    {
        ComplitionCallback();
    }
}
Но чтобы полностью имплементировать такой обьект без сторонних наработок нужно много поработать
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
21.11.2010, 17:36  [ТС] 17
Цитата Сообщение от Manjak Посмотреть сообщение
но сделать универсальный обьект для разнотипных функций с переменным числом параметров
Такая задача не стоит. У меня Execute1, Execute2, Execute3 реализованы внутри одного класса, так что в Execute1 я записываю поля класса, а в Execute2 - читаю. Не самое красивое решение, но достаточно безгеморройное
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
15.04.2011, 19:49  [ТС] 18
Итого по ходу дела я нашёл решение для своего вопроса. Мне это нужно было для Borlnad'а, а у Borland'а есть ацкое расширнеие __closure: http://www.goforvbsix.ru/news/... -01-21-385 По ссылке текст корявый (переведён автоматическим переводчиком), но суть следующая. Если объявить указатель на функцию с модификатором __closure, то такой "указатель" будет содержать в себе на самом деле два указателя: указатель на метод и указатель на экземпляр класса в момент присваивания. Что важно, указатель на метод и указатель на экземпляр класса обезличенные: т.е. никакой информации о конкретном типе нет, а потому такую конструкцию можно протащить через транзитный код. В точке вызова по такому указателю компилятор выдерет из него адрес функции и ажрес экземпляра (по сути дела this) и по своим внутренним правилам построит вызов метода. Именно в этом месте и хранится невозможность использования данной фичи в виде обезличенных двух указателей на void, потому что после этого на языке нет возможности описать вызов метода без подпольных знаний о том, как это делает компилятор. Насколько я понимаю, подобной конструкции в языке Си++ нет, а жаль.

Я ещё не проверял, просто вывел сие из корявых описаний и объяснений на форумах. Надеюсь, что я понял всё-таки правильно

Добавлено через 2 часа 3 минуты
С этой конструкцией код будет выглядеть так:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Base
{
  void Exec (void (__closure *tail)(void))
  {
    // Общие действия
    ...
 
    // Конкретные действия для производных классов
    // здесь уже через tail, который содержит в себе указатель
    // на метод и указатель на экземпляр
    tail();
  }
};
 
class Derived : public Base
{
  void MyExec (void)
  {
    // Выполняем основные действия с двумя разными типами
    // частных действий. Вызов сути эквивалентен
    // "Exec (&this->Tail1)" и в closure-указатель копируется
    // this и адрес метода Tail1
    Exec (Tail1);
    Exec (Tail2);
  }
 
  void Tail1 (void)
  {
    // Определяем конкретные действия "вариант1" для нашего класса
  }
 
  void Tail2 (void)
  {
    // Определяем конкретные действия "вариант2" для нашего класса
  }
};
0
Делаю внезапно и красиво
Эксперт С++
1313 / 1228 / 72
Регистрация: 22.03.2011
Сообщений: 3,744
16.04.2011, 12:45 19
А если вместо указателя на метод передавать функтор? В нём можно и this "точно правильный" передать.)
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
16.04.2011, 14:01  [ТС] 20
Цитата Сообщение от Deviaphan Посмотреть сообщение
А если вместо указателя на метод передавать функтор? В нём можно и this "точно правильный" передать.)
Если я правильно понимаю смысл концепции функтора, то его можно использовать только тогда, когда в точке вызова известен тип. Такое можно делать в шаблонах. Но оно не годится для транзитной передачи в код, не являющийся шаблоном

Если я что-то не так понимаю, то перепиши код из поста #18 с использованием функтора. Для меня важно, чтобы внутренности класса Base НЕ являлись открытыми (т.е. не были шаблоном и не были описаны внутри описания класса)
0
16.04.2011, 14:01
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
16.04.2011, 14:01
Помогаю со студенческими работами здесь

Переопределение виртуального метода
Нужно написать виртуальный метод в родительском классе , который находит площадь круга. Затем...

переопределение виртуального метода
Существует родитель-класс TEditField = class(TObject) protected procedure...

Ошибка создания виртуального метода?
Здравствуйте, хочу сделать так что бы программа(на андроид) рисовала по заданным координатам, но...

Реализовать перегрузку виртуального метода
В класе class1 реализован открытый виртуальный метод деление двух чисел &quot;a&quot; и &quot;b&quot;. Не внося...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru