Pergunta

Eu poderia estar totalmente errado aqui, mas como eu o entendo, C ++ não tem realmente um "ponteiro para função de membro do" tipo nativo. Eu sei que você pode fazer truques com Boost e mem_fun etc. Mas por que os designers do C ++ decidir não ter um ponteiro de 64 bits que contém um ponteiro para a função e um ponteiro para o objeto, por exemplo?

O que quero dizer especificamente é um ponteiro para uma função membro de um particular, objeto de tipo desconhecido . OU SEJA algo que você pode usar para um callback . Este seria um tipo que contém dois valores. O primeiro valor sendo um ponteiro para o função , e o segundo valor ser um ponteiro para o instância específica do objecto.

O que eu não quero dizer é um ponteiro para uma função geral membro de uma classe. Por exemplo.

int (Fred::*)(char,float)

Ele teria sido tão útil e fez a minha vida mais fácil.

Hugo

Foi útil?

Solução

@RocketMagnet - Isto é em resposta à sua outro questão, aquele que foi rotulado como um duplicado. Eu estou respondendo a que questão, não esta.

De uma maneira geral, ponteiro C ++ para funções de membro pode não portably ser moldada através da hierarquia de classe. Dito isto muitas vezes você pode começar afastado com ele. Por exemplo:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};

int main() {
    typedef void (A::*pmf_t)();
    C c; c.x = 42; c.y = -1;

    pmf_t mf = static_cast<pmf_t>(&C::foo);
    (c.*mf)();
}

compilar este código, eo compilador justamente reclama:

$ cl /EHsc /Zi /nologo pmf.cpp
pmf.cpp
pmf.cpp(15) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code

$

Então, para responder "por que não C ++ tem um vazio classe ponteiro-para-membro-função-on-?" é que esta base de classe-de-tudo imaginário não possui membros, por isso não há valor que você pode atribuir com segurança a ele! "Void (C :: ) ()" e "(void :: ) ()" são tipos mutuamente incompatíveis.

Agora, eu aposto que você está pensando "Espere, eu já lançou-função de membro ponteiros bem antes!" Sim, você pode ter, usando reinterpret_cast e herança única. Este está na mesma categoria de outros moldes reinterpretar:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};
class D { public: int z; };

int main() {
    C c; c.x = 42; c.y = -1;

    // this will print -1
    D& d = reinterpret_cast<D&>(c);
    cout << "d.z == " << d.z << "\n";
}

Então, se void (void::*)() existia, mas não há nada que você poderia seguramente / portably atribuir a ele.

Tradicionalmente, você usa funções de void (*)(void*) assinatura em qualquer lugar que tinha coisa de usar void (void::*)(), porque enquanto-função de membro ponteiros não bem lançado para cima e para baixo da hierarquia de herança, ponteiros void não bem lançada. Em vez disso:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};

void do_foo(void* ptrToC){
    C* c = static_cast<C*>(ptrToC);
    c->foo();
}

int main() {
    typedef void (*pf_t)(void*);
    C c; c.x = 42; c.y = -1;

    pf_t f = do_foo;
    f(&c);
}

Assim, para sua pergunta. Por que não C ++ apoiar este tipo de fundição. tipos ponteiro-para-função-membro já tem que lidar com o Virtual vs classes de base não-virtuais, e virtual vs funções de membro não-virtuais, tudo no mesmo tipo, inflando-los a 4 * sizeof (* void) em algumas plataformas. Acho que é porque ele iria complicar ainda mais a implementação de ponteiro-para-função-membro, e ponteiros de função matérias já resolver este problema tão bem.

Como outros têm comentou, C ++ dá escritores biblioteca ferramentas suficientes para conseguir este feito, e então os programadores 'normais' como você e eu deveria usar essas bibliotecas em vez de suar esses detalhes.

EDIT: marcado wiki comunidade. Por favor, só edição para incluir referências relevantes para o padrão C ++, e adicionar em itálico. (Esp. Adicionar referências a um padrão onde o meu entendimento estava errado! ^ _ ^)

Outras dicas

Como outros apontaram, C ++ tem um tipo de membro ponteiro de função.

O termo que você procura é "função ligada". A razão C ++ não fornece açúcar sintaxe para a ligação função é por causa de sua filosofia de apenas fornecendo as ferramentas mais básicas, com o qual você pode então construir tudo o que quiser. Isso ajuda a manter a língua "pequeno" (ou, pelo menos, menos importava bogglingly enorme).

Da mesma forma, C ++ não tem um bloqueio {} primitiva como C # 's mas tem RAII que é usado por scoped_lock de impulso.

Há, naturalmente, a escola de pensamento que diz que você deve adicionar açúcar sintaxe para tudo o que possa ser de uso. Para melhor ou pior, C ++ não pertence a essa escola.

Eu acho que o que você está procurando pode ser nestes librairies ...

Os delegados rápidos http://www.codeproject.com/KB/cpp/FastDelegate aspx

http: //www.boost. org / doc / libs / 1_37_0 / doc / html / function.html

E aqui está uma explicação muito completa das questões relacionadas ponteiro de função http://www.parashift.com/c++-faq-lite/pointers-to-members.html

Ele faz.

Por exemplo,

int (Fred::*)(char,float)

é um ponteiro para uma função membro de uma Fred classe que retorna um int e leva um char e uma float.

Eu acho que a resposta é que os designers do C ++ optar por não ter em linguagem as coisas que poderiam ser facilmente implementado em uma biblioteca. Sua própria descrição do que você quer dá uma maneira perfeitamente razoável para implementá-lo.

Eu sei que soa engraçado, mas C ++ é uma linguagem minimalista. Eles deixou a bibliotecas tudo o que podiam sair a eles.

O TR1 tem std :: tr1 :: function, e será adicionado ao C ++ 0x. Assim, em certo sentido, tem-lo.

Uma das filosofias de design de C ++ é: você não paga para o que você não usa. O problema com C delagates # estilo é que eles são pesados ??e exigem o suporte ao idioma, que todos iriam pagar se eles usaram ou não. É por isso que a implementação da biblioteca é o preferido.

Os delegados razão são pesados ??é que um ponteiro método é muitas vezes maior do que um ponteiro normal. Isso acontece sempre que o método é virtual. O ponteiro método irá chamar uma função diferente dependendo do que classe base usa-lo. Isso requer pelo menos dois ponteiros, o vtable e o deslocamento. Há outra estranheza envolvido com o método é de uma classe envolvida na herança múltipla.

Tudo o que disse, eu não sou um escritor do compilador. Pode ter sido possível fazer um novo tipo de ponteiros método vinculado que subverteria o virtual-ness do método referenciado (após todos nós sabemos o que a classe base é se o método é obrigado).

A questão certamente não é o básico de ter um ponteiro de objeto e um ponteiro de função em um pacote fácil de usar, porque você pode fazer isso usando um ponteiro e uma conversão. (Esses thunks já são utilizados por VC ++ em x86 para ponteiros de apoio para funções membro virtual, de modo que estes ponteiros só ocupam 4 bytes.) Você pode acabar com um monte de thunks, é verdade, mas as pessoas já contam com o vinculador eliminar instantiations modelo duplicados - eu, de qualquer maneira - e só há tantos vtable e isso compensa você vai acabar com a prática. A sobrecarga provavelmente não seria significativo para qualquer programa de tamanho razoável, e se você não usar este material, então não vai custar nada.

(Arquiteturas que tradicionalmente usam um TOC seria armazenar o ponteiro do TOC na parte ponteiro de função, em vez de na conversão, assim como eles já teria de fazer.)

(Este novo tipo de objeto não seria exatamente substituível por um ponteiro normal para a função, é claro, porque o tamanho seria diferente. Eles, no entanto, ser escrita da mesma no ponto de chamada.)

O problema que eu vejo é que da convenção de chamada: apoiar ponteiros para funções desta forma pode ser complicado no caso geral, porque o código gerado teria que preparar os argumentos (este incluído) da mesma forma sem levar em conta o tipo real de coisa, função ou função membro, para o qual o ponteiro aponta.

Isto provavelmente não é um grande negócio em x86, pelo menos não com thiscall, porque você pode apenas carregar ECX independentemente e aceito que se a função de chamada não precisa dele, então ele será falso. (E eu acho VC ++ assume ECX é falso, neste caso, de qualquer maneira.) Mas em arquiteturas que passam argumentos para parâmetros nomeados em registos de funções, você pode acabar com uma boa quantidade de baralhar na conversão, e se os argumentos da pilha são empurrados esquerda -a-direito, então você está basicamente recheado. E isso não pode ser reparado acima estaticamente, porque, no limite, não há informações cross-tradução-unidade.

[EDIT: MSalters, em um comentário para o rocketmagnet post acima, aponta que, se ambos objeto e função são conhecidos, então a esta compensação e assim por diante pode ser determinado imediatamente. Esta totalmente não me ocorreu! Mas, com isso em mente, suponho que só precisa de ser armazenado o ponteiro exato objeto, talvez offset, e o ponteiro de função exata. Isso faz com que thunks totalmente desnecessário - eu acho -. Mas eu tenho certeza que as questões de apontando para funções de membro e funções não-membros igualmente permaneceria]

C ++ já é uma grande linguagem, e adicionando este teria feito maior. O que você realmente quer é ainda pior do que apenas uma função membro limite, é algo mais próximo de boost :: função. Você deseja armazenar tanto um void(*)() e um par de chamadas de retorno. Afinal de contas, a razão é que você quer o dar o chamador uma chamada de retorno completa, eo receptor não deve se preocupar com os detalhes exatos.

O tamanho seria provavelmente sizeof(void*)+sizeof(void(*)()). Ponteiros para funções de membro pode ser maior, mas isso é porque eles são não ligado. Eles precisam lidar com a possibilidade de que você está' tomando o endereço de uma função virtual, por exemplo. No entanto, um tipo ligado-ponteiro-para-função membro embutido não sofrem desta sobrecarga. Ele pode resolver a função exata a ser chamado no momento da ligação.

Isso não é possível com um UDT. impulso :: função não pode descartar a sobrecarga de um PTMF quando se liga o ponteiro de objecto. Você precisa saber entender a estrutura de um PTMF, vtable, etc. - tudo coisas fora do padrão. No entanto, podemos chegar lá agora com C ++ 1x. Uma vez que está em std ::, é um jogo justo para os fornecedores do compilador. A própria implementação da biblioteca padrão não é portátil (ver por exemplo type_info).

Você ainda gostaria de ter alguma sintaxe bom, eu acho que, mesmo que um compilador de fornecedores implementa esta na biblioteca. Eu gostaria std::function<void(*)()> foo = &myX && X::bar. (Ele não colidir com a sintaxe existente, como X :: bar não é uma expressão - única & X :: bar é)

Ocorre-me que os métodos têm um argumento implícito this, então um ponteiro c a um método é insuficiente para permitir que o método a ser chamado (porque não há nenhuma maneira de determinar qual instância deve ser usado para this (ou mesmo se qualquer instância é atualmente existente)).

Editar: Rocketmagnet comentários que dirigiu esta na pergunta, e que parece ser o caso, embora eu acho que foi adicionado depois que eu comecei esta resposta. mas eu vou dizer "mea culpa", de qualquer maneira.

Assim, permitam-me a expandir o pensou um pouco.

C ++ está intimamente relacionado com c, e tem todos os seus tipos intrínsecos compatível com a linguagem mais cedo (em grande parte por causa da história do desenvolvimento c++, suponho). Assim, um ponteiro c++ intrínseca é um ponteiro c, e é incapaz de suportar o uso que você pedir.

Certamente você poderia construir um tipo derivado para fazer o trabalho --- como na implementação boost --- mas tal criatura pertence em uma biblioteca.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top