указатель на функцию-член - PullRequest
5 голосов
/ 31 мая 2011

Мне нужно использовать указатель на функцию-член, который принимает аргумент базового класса, который используется в другом коде. Ну, просто я хочу сделать что-то вроде примера ниже. Этот код работает нормально, но мне интересно, всегда ли такое приведение безопасно? Я не могу выполнить здесь dynamic или static.

#include <cstdio>                                                   

class C
{                                                           
public:                                                             
        C () : c('c') {}                                            
        virtual ~C() {}                                             

        const char c;                                               
};                                                                  

class D : public C
{                                                
public:                                                             
        D () : d('d') {}                                            
        virtual ~D() {}                                             

        const char d;                                               
};                                                                  

class A 
{                                                           
public:                                                             
        A () {}                                                     
        virtual ~A() {}                                             

        void f( C& c ) { printf("%c\n",c.c); }                      
        void g( D& d ) { printf("%c %c\n",d.c,d.d); }               
};                                                                  

int main (int argc, char const* argv[])                             
{                                                                   
        void (A::*pf)( C& c ) = &A::f;                              
        void (A::*pg)( D& d ) = reinterpret_cast<void (A::*)(D&)>(&A::f);

        A a;                                                        
        C c;                                                        
        D d;                                                        

        (a.*pf)( c );                                               
        (a.*pg)( d );                                               

        return 0;                                                   
}                                                              

Ответы [ 4 ]

2 голосов
/ 31 мая 2011

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

В вашей ситуации правильный способ ее реализации - ввестипромежуточная функция для преобразования типов параметров

class A 
{                                                           
public:          
  ...                                                   
  void f( C& c ) { printf("%c\n",c.c); }                      
  void f_with_D( D& d ) { f(d); }
  ...
};          

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

void (A::*pg)( D& d ) = &A::f_with_D;

сейчас

A a;
D d;                                                        
(a.*pg)( d );

в конечном итоге вызовет a.f с C подобъектом объекта d в качестве аргумента.

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

class A 
{                                                           
public:          
  ...                                                   
  void f( C& c ) { printf("%c\n",c.c); }                      
  void f( D& d ) { f(static_cast<C&>(d)); }
  ...
};          

Без преобразования вы закончитеA::f(D&) вызывает себя рекурсивно.

1 голос
/ 31 мая 2011

Компилятор должен отклонить код с написанным вами dynamic_cast.(Я думаю, что это была опечатка, и вы имели в виду reinterpret_cast, учитывая ваш вводный текст.)оригинал).Таким образом, мы находимся в неопределенной области для результата приведения и в неопределенной области для поведения при вызове результата приведения.

1 голос
/ 31 мая 2011

Нет, ваш пример не работает нормально.
Во-первых, вы можете использовать dynamic_cast только для приведения между связанными типами классов , а не чем-то другим.
Во-вторых, даже если вызамените это dynamic_cast на reinterpret_cast или приведение в стиле C (что, я полагаю, вы имели в виду), тогда я получу следующий вывод:

c
c

Не совсем то, что вы хотели.

Почему это даже работает и не приводит к ужасным сбоям, потому что «безопасно» переходить назад и вперед между указателями на функции-члены, никакая информация не будет потеряна.
Почему он по-прежнему печатает что-то, потому что компилятор не видит ошибок с типами, но сборка не заботится о типах, она заботится только об адресах, поэтому она по-прежнему будет вызывать A::f, потому что это указатель, который высохранено, независимо от типа.

Интересно, что это все еще работает, даже если вы не связаны классы (D не наследует от C), опять же, потому что сборка не заботится о типах.Изменение функций в A следующим образом:

void f( C& c ) { printf("f(C& c): %c\n",c.c); }
void g( D& d ) { printf("g(D& d): %c\n",d.d); }

приводит к следующему выводу:

f (C & c): c
f (C & c):d

«Как это работает? D даже не имеет c члена!».Ну, опять же из-за адресов.Обе переменные имеют одинаковое смещение от указателя this, а именно +0.Теперь давайте добавим еще один член в C (упрощенный класс):

struct C{
        C () : c('c') {}
        int i; // mean
        const char c;
};

И попробуйте еще раз, выведите:

f (C & c): c
f(C & C): 10

Да, мы идем.C::c теперь находится по смещению +4 (+0 + sizeof int), а printf считывает оттуда.В D такого смещения нет, и printf читает из неинициализированной памяти.С другой стороны, доступ к неинициализированной памяти неопределенное поведение .

Итак, чтобы окончательно прийти к выводу: нет, это небезопасно.:)

0 голосов
/ 31 мая 2011

Вам нужно использовать reinterpret_cast, чтобы это заработало. В этом случае он должен быть безопасным (см. Примечания), но он может потерпеть неудачу, если используется множественное наследование, поскольку указатели необходимо будет корректировать при передаче D как C. Компилятор должен знать, что это должно произойти, что в данном случае невозможно (вызов pg с d просто пропустит этот шаг, функция-член получит неизмененный адрес объекта D).

Замечания : я сказал safe - ну, на самом деле это неопределенное поведение из-за reinterpret_cast, переводящего тип в несвязанный тип и использующего этот тип, но тем не менее он должен работать на большинстве компиляторы. Пожалуйста, пожалуйста, не делайте этого в производственном коде.

...