Pergunta

Em C++, os ponteiros de função de membro podem ser usados ​​para apontar para membros de classe derivados (ou mesmo básicos)?

EDITAR:Talvez um exemplo ajude.Suponha que temos uma hierarquia de três classes X, Y, Z em ordem de herança.Y portanto tem uma classe base X e uma classe derivada Z.

Agora podemos definir um ponteiro de função membro p para classe Y.Isso está escrito como:

void (Y::*p)();

(Para simplificar, assumirei que estamos interessados ​​apenas em funções com a assinatura void f() )

Este ponteiro p agora pode ser usado para apontar para funções-membro da classe Y.

Esta questão (duas questões, na verdade) é então:

  1. Pode p ser usado para apontar para uma função na classe derivada Z?
  2. Pode p ser usado para apontar para uma função na classe base X?
Foi útil?

Solução

C++03 padrão, §4.11 2 Indicador para conversões de membros:

Um rvalue do tipo “ponteiro para membro de B do tipo cv T”, onde B é um tipo de classe, pode ser convertido em um rvalue do tipo “ponteiro para membro de D do tipo cv T”, onde D é uma classe derivada (cláusula 10) de B.Se B for uma classe base inacessível (cláusula 11), ambígua (10.2) ou virtual (10.1) de D, um programa que necessita dessa conversão está mal formado.O resultado da conversão refere-se ao mesmo membro que o ponteiro para o membro antes da conversão ocorrer, mas refere-se ao membro da classe base como se fosse um membro da classe derivada.O resultado refere-se ao membro na instância de B de D.Como o resultado tem o tipo “ponteiro para membro de D do tipo cv T”, pode ser desreferenciado com um objeto D.O resultado é o mesmo como se o ponteiro para o membro de B fosse desreferenciado com o subobjeto B de D.O valor do ponteiro de membro nulo é convertido no valor do ponteiro de membro nulo do tipo de destino. 52)

52)A regra para conversão de ponteiros em membros (de ponteiro para membro de base, para ponteiro para membro de derivado) parece invertida em comparação com a regra para ponteiros para objetos (de ponteiro para derivado, para ponteiro para base) (4.10, cláusula 10).Esta inversão é necessária para garantir a segurança do tipo.Observe que um ponteiro para membro não é um ponteiro para objeto ou um ponteiro para função e as regras para conversões de tais ponteiros não se aplicam a ponteiros para membros.Em particular, um ponteiro para membro não pode ser convertido em void*.

Resumindo, você pode converter um ponteiro para um membro de uma classe base acessível e não virtual em um ponteiro para um membro de uma classe derivada, desde que o membro não seja ambíguo.

class A {
public: 
    void foo();
};
class B : public A {};
class C {
public:
    void bar();
};
class D {
public:
    void baz();
};
class E : public A, public B, private C, public virtual D {
public: 
    typedef void (E::*member)();
};
class F:public E {
public:
    void bam();
};
...
int main() {
   E::member mbr;
   mbr = &A::foo; // invalid: ambiguous; E's A or B's A?
   mbr = &C::bar; // invalid: C is private 
   mbr = &D::baz; // invalid: D is virtual
   mbr = &F::bam; // invalid: conversion isn't defined by the standard
   ...

Conversão na outra direção (via static_cast) é regido por § 5.2.9 9:

Um rvalue do tipo "ponteiro para membro de D do tipo cv1 T" pode ser convertido em um rvalue do tipo "ponteiro para membro de B do tipo cv2 T", onde B é uma classe base (cláusula 10 classe.derivada) de D, se existir uma conversão padrão válida de "ponteiro para membro de B do tipo T" para "ponteiro para membro de D do tipo T" (4.11 memória conv.), e cv2 é a mesma qualificação cv ou maior qualificação cv que, cv1.11) O valor do ponteiro de membro nulo (4.11 memória conv.) é convertido no valor do ponteiro de membro nulo do tipo de destino.Se a classe B contém o membro original, ou é uma classe base ou derivada da classe que contém o membro original, o ponteiro resultante para o membro aponta para o membro original.Caso contrário, o resultado da conversão será indefinido.[Observação:embora a classe B não precise conter o membro original, o tipo dinâmico do objeto no qual o ponteiro para o membro é desreferenciado deve conter o membro original;ver 5.5 expr.mptr.oper.]

11) Os tipos de função (incluindo os usados ​​no ponteiro dos tipos de função de membro) nunca são qualificados para CV;ver 8.3.5 dcl.fct.

Resumindo, você pode converter de um derivado D::* para uma base B::* se você pode converter de um B::* para um D::*, embora você só possa usar o B::* em objetos que são do tipo D ou descendentes de D.

Outras dicas

Não tenho 100% de certeza do que você está perguntando, mas aqui está um exemplo que funciona com funções virtuais:

#include <iostream>
using namespace std;

class A { 
public:
    virtual void foo() { cout << "A::foo\n"; }
};
class B : public A {
public:
    virtual void foo() { cout << "B::foo\n"; }
};

int main()
{
    void (A::*bar)() = &A::foo;
    (A().*bar)();
    (B().*bar)();
    return 0;
}

O problema crítico com ponteiros para membros é que eles podem ser aplicados a qualquer referência ou ponteiro para uma classe do tipo correto.Isto significa que porque Z é derivado de Y um ponteiro (ou referência) do tipo ponteiro (ou referência) para Y pode realmente apontar (ou referir-se) ao subobjeto da classe base de Z ou qualquer outra aula derivado de Y.

void (Y::*p)() = &Z::z_fn; // illegal

Isso significa que qualquer coisa atribuída a um ponteiro para um membro de Y deve realmente funcionar com qualquer Y.Se fosse permitido apontar para um membro do Z (que não era membro de Y) então seria possível chamar uma função membro de Z em alguma coisa que não era realmente um Z.

Por outro lado, qualquer ponteiro para membro de Y também aponta o membro do Z (herança significa que Z tem todos os atributos e métodos de sua base) é legal converter um ponteiro em membro de Y para um ponteiro para membro de Z.Isso é inerentemente seguro.

void (Y::*p)() = &Y::y_fn;
void (Z::*q)() = p; // legal and safe

Você pode querer verificar este artigo Ponteiros de função de membro e os delegados C++ mais rápidos possíveis A resposta curta parece ser sim, em alguns casos.

Eu acredito que sim.Como o ponteiro de função usa a assinatura para se identificar, o comportamento base/derivado dependeria de qualquer objeto em que você o chamasse.

Minha experimentação revelou o seguinte:Aviso - este pode ser um comportamento indefinido.Seria útil se alguém pudesse fornecer uma referência definitiva.

  1. Isso funcionou, mas exigiu uma conversão ao atribuir a função de membro derivada a p.
  2. Isso também funcionou, mas exigiu conversões extras ao desreferenciar p.

Se nos sentirmos realmente ambiciosos, poderíamos perguntar se p pode ser usado para apontar para funções de membro de classes não relacionadas.Eu não tentei, mas o Delegado Rápido a página vinculada à resposta de dagorym sugere que é possível.

Concluindo, tentarei evitar o uso de ponteiros para funções-membro dessa maneira.Passagens como as seguintes não inspiram confiança:

O elenco entre os ponteiros da função de membro é uma área extremamente escurosa.Durante a padronização do C ++, houve muita discussão sobre se você deve ser capaz de lançar um ponteiro de função de membro de uma classe para um ponteiro de função de membro de uma classe ou classe derivada e se você poderia lançar entre classes não relacionadas.Quando o Comitê de Padrões decidiu, diferentes fornecedores de compiladores já haviam tomado decisões de implementação que os prenderam em diferentes respostas a essas perguntas.[Artigo FastDelegate]

Suponha que temos class X, class Y : public X, and class Z : public Y

Você deve ser capaz de atribuir métodos para X e Y a ponteiros do tipo void (Y::*p)() mas não métodos para Z.Para ver por que considere o seguinte:

void (Y::*p)() = &Z::func; // we pretend this is legal
Y * y = new Y; // clearly legal
(y->*p)(); // okay, follows the rules, but what would this mean?

Ao permitir essa atribuição, permitimos a invocação de um método para Z em um objeto Y que poderia levar sabe-se lá a quê.Você pode fazer tudo funcionar lançando os ponteiros, mas isso não é seguro ou garantido que funcione.

Aqui está um exemplo do que funciona.Você pode substituir um método na classe derivada, e outro método da classe base que usa um ponteiro para esse método substituído, de fato, chama o método da classe derivada.

#include <iostream>
#include <string>

using namespace std;

class A {
public:
    virtual void traverse(string arg) {
        find(&A::visit, arg);
    }

protected:
    virtual void find(void (A::*method)(string arg),  string arg) {
        (this->*method)(arg);
    }

    virtual void visit(string arg) {
        cout << "A::visit, arg:" << arg << endl;
    }
};

class B : public A {
protected:
    virtual void visit(string arg) {
        cout << "B::visit, arg:" << arg << endl;
    }
};

int main()
{
    A a;
    B b;
    a.traverse("one");
    b.traverse("two");
    return 0;
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top