Указатели на функции-члены в иерархии - PullRequest
4 голосов
/ 08 апреля 2010

Я использую библиотеку, которая определяет интерфейс:

template<class desttype>
void connect(desttype* pclass, void (desttype::*pmemfun)());

а у меня небольшая иерархия

class base {
   void foo();
};

class derived: public base { ... };

В функции-члене derived я хочу вызвать

connect(this, &derived::foo);

но кажется, что &derived::foo на самом деле является указателем на функцию-член base; gcc выплевывает

error: no matching function for call to ‘connect(derived* const&, void (base::* const&)())’

Я могу обойти это, явно приведя this к base *; но почему компилятор не может сопоставить вызов с desttype = base (поскольку derived * может быть неявно приведен к base *)?

Кроме того, почему является &derived::foo не указателем функции-члена derived?

Ответы [ 3 ]

7 голосов
/ 08 апреля 2010

Во-первых, когда вы делаете &class::member, тип результата всегда основан на классе, в котором фактически объявлен член. Вот как унарный & работает в C ++.

Во-вторых, код не компилируется из-за сбоя вывода аргумента шаблона. Из первого аргумента он выводит desttype = derived, а из второго он выводит desttype = base. Это то, что делает сборку неудачной. Правила вывода аргументов шаблона в C ++ не учитывают тот факт, что this может быть преобразовано в тип base *. Более того, можно утверждать, что вместо преобразования типа this в base * правильным будет преобразование &derived::foo из указателя на базовый элемент в тип указателя на производный член. Оба подхода одинаково жизнеспособны (см. Ниже).

В-третьих, указатели членов в C ++ подчиняются правилам contra-variance , что означает, что указатель на член базового класса может быть неявно преобразован в указатель на член производного класса. В вашем случае все, что вам нужно сделать, это помочь компилятору пройти вывод аргумента шаблона, указав аргумент явно, и код должен скомпилировать

 connect<derived>(this, &derived::foo);

Вышеприведенное должно скомпилироваться из-за противоположного отклонения от &derived::foo указателя, даже если это указатель на base член. В качестве альтернативы вы можете сделать

 connect<base>(this, &derived::foo);

Это также должно компилироваться из-за ковариации из this указателя.

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

0 голосов
/ 08 апреля 2010

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

По моему опыту, лучший способ обойти это - объявить два аргумента шаблона для функции:

template<typename Y, typename T>
void connect(Y * pclass, void (T::*pmemfun)());

В этом случае компилятор может автоматически создать для вас экземпляр

void connect<derived, base>(derived * pclass, void (base::*pmemfun)());

Это решение также совершенно безопасно, поскольку преобразование из производного * в базовое * будет выполнено внутри connect (где я предполагаю, что вы вызываете pclass -> * pmemfun ())

0 голосов
/ 08 апреля 2010

Указатели на функции-члены имеют много специфических особенностей в C ++, и различные компиляторы имеют несоответствия в том, как они работают. Статья Дуга Клагстона, "Указатели на функции-члены и максимально быстрые делегаты C ++" , представляет собой очень хороший обзор того, как они работают (и не работают):

при работе с производными классами, Есть некоторые сюрпризы. Например, код ниже будет скомпилирован на MSVC, если Вы оставляете комментарии без изменений:

class SomeClass {
 public: 
    virtual void some_member_func(int x, char *p) {
       printf("In SomeClass"); };
};

class DerivedClass : public SomeClass {
 public:
 // If you uncomment the next line, the code at line (*) will fail!

//    virtual void some_member_func(int x, char *p) { printf("In DerivedClass"); };

};

int main() {
    // Declare a member function pointer for SomeClass

    typedef void (SomeClass::*SomeClassMFP)(int, char*);
    SomeClassMFP my_memfunc_ptr;
    my_memfunc_ptr = &DerivedClass::some_member_func; // ---- line (*)
}

Как ни странно, &DerivedClass::some_member_func является указатель на функцию-член класса SomeClass. Не является членом DerivedClass! (Некоторые компиляторы ведут себя немного по-другому: например, для Digital Mars C ++, &DerivedClass::some_member_func есть не определено в этом случае.) Но, если DerivedClass переопределяет some_member_func, код не будет компилировать, потому что &DerivedClass::some_member_func имеет теперь станьте указателем на функцию-член класса DerivedClass!

...