Padrão C++

Pontos para funções de membro

É o tipo de “ponteiro para função de membro” diferente de “ponteiro para função”?

Yep.

Considerar a seguinte função:

int f(char a, float b);

O tipo desta função é diferente dependendo se é uma função normal ou uma nãostatic função de membro de alguma classe:

Nota: se é uma função de membro de static função de membro de class Fred, o seu tipo é o mesmo que se fosse uma função normal:”int (*)(char,float)“.

Como passo um ponteiro para a função de membro a um manipulador de sinal, chamada de retorno de evento X, chamada de sistema que inicia um thread/tarefa, etc?

Não.

Porque uma função membro não tem sentido sem um objeto para invocá-la, você não pode fazer isso diretamente (se o X WindowSystem fosse reescrito em C++, ele provavelmente passaria referências a objetos ao redor, não apenas apontadores para funções; naturalmente os objetos incorporariam a função requerida e provavelmente muito mais).

Como um patch para software existente, use uma função de nível superior (não membro) como um wrapper que pega um objeto obtido através de alguma outra técnica. Dependendo da rotina que você está chamando, essa “outra técnica” pode ser trivial ou pode exigir um pouco de trabalho de sua parte. A chamada de sistema que inicia uma thread, por exemplo, pode exigir que você passe o ponteiro de afunção junto com um void*, assim você pode passar o ponteiro de objeto no void*. Muitos sistemas de operação em tempo real fazem algo semelhante para a função que inicia uma nova tarefa. Na pior das hipóteses você pode armazenar o ponteiro de objeto em uma variável global; isto pode ser necessário para manipuladores de sinal Unix (mas os globais são, em geral, indesejáveis). Em qualquer caso, a função de nível superior chamaria a função de membro desejada no objeto.

Aqui está um exemplo do pior caso (usando um global). Suponha que você queira chamar Fred::memberFn() na interrupção:

Nota: static funções de membro não requerem que um objeto real seja invocado, sopointerss-tostatic-funções de membro são geralmente compatíveis com o tipo de ponteiro para-funções regulares. No entanto, embora provavelmente funcione na maioria dos compiladores, na verdade teria de ser uma função extern "C" não-membro para se tornar correcta, uma vez que “C linkage” não cobre apenas coisas como o mangling de nomes, mas também chamadas convenções, que podem ser diferentes entre C e C++.

Por que continuo a receber erros de compilação (tipo mismatch) quando tento usar uma função de membro como uma rotina de interrupção de serviço?

Este é um caso especial das duas perguntas anteriores, portanto leia primeiro as duas respostas anteriores.

Non-static funções de membro têm um parâmetro oculto que corresponde ao ponteiro this. O ponteiro this aponta para os dados da instância para o objeto. O hardware/firmware de interrupção no sistema não é capaz de fornecer o argumentothis ponteiro. Você deve usar funções “normais” (não membros da classe) ou static funções de membro como rotinas de interrupção de serviço.

Uma solução possível é usar um membro static como rotina de interrupção de serviço e fazer essa função procurar em algum lugar para encontrar o par instância/membro que deve ser chamado na interrupção. Assim o efeito é que uma função de membro é invocada em uma interrupção, mas por razões técnicas você precisa chamar uma função intermediária primeiro.

Por que estou tendo problemas para pegar o endereço de uma função C++?

Resposta curta: se você está tentando armazená-la (ou passá-la como) um ponteiro para a função, então esse é o problema – este é um corolário da FAQ anterior.

Resposta longa: Em C++, as funções membro têm um parâmetro implícito que aponta para o objeto (o this ponteiro dentro da função membro). Funções normais em C podem ser pensadas como tendo uma convenção de chamadas diferente das funções de membro, portanto os tipos de seus ponteiros (pointer-to-member-function vs pointer-to-function) são diferentes e incompatíveis. C++ introduz um novo tipo de ponteiro, chamado ponteiro para membro, que só pode ser invocado por meio de um objeto.

NOTE: não tente “fundir” um ponteiro para função de membro em um ponteiro para função; o resultado é indefinido e provavelmente desastroso. Por exemplo, um ponteiro para a função de membro não é necessário para conter o endereço da máquina da função apropriada. Como foi dito no último exemplo, se você tem um ponteiro para uma função C regular, use ou uma função de nível superior (não membro), ou uma função de membro static (classe).

Como posso evitar erros de sintaxe ao criar ponteiros para membros?

Use um typedef.

Sim, certo, eu sei: você é diferente. Você é inteligente. Você pode fazer essas coisas sem um typedef. Suspira. Recebi muitos e-mails de pessoas que, como você, se recusaram a seguir o simples conselho desta FAQ. Eles desperdiçaram horas e horas do seu tempo, quando 10 segundos no valor de typedefs teriam simplificado as suas vidas. Além disso, encare, você não está escrevendo código que só você pode ler; você está escrevendo seu código que outros também poderão ler – quando estiverem cansados – quando tiverem seus próprios prazos e seus próprios desafios. Então, por que intencionalmente tornar a vida mais difícil para si mesmo e para os outros? Seja inteligente: use um typedef.

Aqui está uma classe de exemplo:

class Fred {public: int f(char x, float y); int g(char x, float y); int h(char x, float y); int i(char x, float y); // ...};

O typedef é trivial:

typedef int (Fred::*FredMemFn)(char x, float y); // Please do this!

É isso! FredMemFn é o nome do tipo, e um ponteiro desse tipo aponta para qualquer membro de Fred que leva(char,float), como Fred‘s f, g, h e i.

É então trivial declarar um apontador de função membro:

int main(){ FredMemFn p = &Fred::f; // ...}

E também é trivial declarar funções que recebem apontadores de função membro:

void userCode(FredMemFn p){ /*...*/ }

E também é trivial declarar funções que retornam apontadores de função membro:

FredMemFn userCode(){ /*...*/ }

Então, por favor, use um typedef. Ou isso ou não me envie um e-mail sobre os problemas que você tem com seus apontadores de funções de membro!

Como posso evitar erros de sintaxe ao chamar uma função de membro usando um apontador para função de membro?

Se você tiver acesso a um compilador e biblioteca padrão que implemente as partes apropriadas do próximo padrão C++17, use std::invoke. Caso contrário, use uma macro.#define macro.

Por favor.

Pretty please.

Eu recebo demasiados emails de pessoas confusas que se recusaram a seguir este conselho. É tão simples. Eu sei, você não precisastd::invoke ou uma macro, e o especialista com quem você falou pode fazer isso sem nenhum deles, mas por favor não deixe o seu ego atrapalhar o que é importante: dinheiro. Outros programadores vão precisar de ler / manter o seu código. Sim, eu sei: você é mais esperto que todos os outros; tudo bem. E tu és espectacular; óptimo. Mas não acrescente complexidade desnecessária ao seu código.

Usar std::invoke é trivial. Nota: FredMemFn é um typedef para um tipo de ponteiro para membro:

Se você não pode usar std::invoke, reduza o custo de manutenção, paradoxalmente, usando uma macro #define neste caso em particular.

(Normalmente eu não gosto de #define macros, mas você deve usá-las com apontadores tomembers porque elas melhoram a legibilidade e a capacidade de escrita desse tipo de código.)

A macro é trivial:

#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))

Usar a macro também é trivial. Nota: FredMemFn é um typedef para um tipo de ponteiro para membro:

A razão std::invoke ou esta macro é uma boa ideia é porque as invocações de funções de membro são muitas vezes muito mais complexas do que o exemplo simples que acaba de ser dado. A diferença em legibilidade e capacidade de escrita é significativa.comp.lang.c++ teve de suportar centenas e centenas de lançamentos de programadores confusos que não conseguiriam obter a sintaxe correcta. Quase todos estes erros teriam desaparecido se tivessem usado std::invoke ou a macro acima.

Nota: #define macros são maus de 4 maneiras diferentes: mal#1,mal#2, mal#3, e mal#4. Mas, às vezes, elas ainda são úteis. Mas você ainda deve sentir uma vaga sensação de vergonha depois de usá-las.

Como eu crio e uso um array de funções de ponteiro para membro?

Utilize tanto a typedef como a std::invoke ou a #define macro descrita anteriormente, e você está 90% feito.

Passo 1: crie um typedef:

Passo 2: crie uma macro #define se você não tiver std::invoke:

#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))

Agora o seu conjunto de funções de ponteiro para membro é simples:

FredMemFn a = { &Fred::f, &Fred::g, &Fred::h, &Fred::i };

E o seu uso de um dos apontadores de funções de membro também é simples:

void userCode(Fred& fred, int memFnNum){ // Assume memFnNum is between 0 and 3 inclusive: std::invoke(a, fred, 'x', 3.14);}

ou se não tiver std::invoke,

void userCode(Fred& fred, int memFnNum){ // Assume memFnNum is between 0 and 3 inclusive: CALL_MEMBER_FN(fred, a) ('x', 3.14);}

Nota: #define macros são más de 4 maneiras diferentes: mal#1,mal#2, mal#3, e mal#4. Mas, às vezes, ainda são úteis. Sinta-se envergonhado, sinta culpa, mas quando uma construção maligna como uma macro melhora o seu software, useit.

Como eu declaro um ponteiro-para-função de membro que aponta para uma função de membro constante?

Resposta curta: adicione um const à direita do ) quando você usa um typedef para declarar o ponteiro-para-função de membro.

Por exemplo, suponha que você queira uma função de ponteiro para membro que aponte para Fred::f, Fred::g ou Fred::h:

class Fred {public: int f(int i) const; int g(int i) const; int h(int j) const; // ...};

Então quando você usar um typedef para declarar o tipo membro-ponto-função, deve parecer com isto:

É isso!

Então você pode declarar/passar/retornar apontadores de função de membro como normal:

Qual é a diferença entre os operadores .* e ->*?

Você não precisará entender isso se você usar std::invoke ou uma macro para chamadas de ponteiro de função de membro. Ohyea, por favor use std::invoke ou uma macro neste caso. E eu mencionei que você deve usar std::invoke ou uma macro neste caso?!?

Por exemplo:

Mas por favor considere usar um std::invoke ou uma macro em vez disso:

void sample(Fred x, Fred& y, Fred* z, FredMemFn func){ std::invoke(func, x, 42, 3.14); std::invoke(func, y, 42, 3.14); std::invoke(func, *z, 42, 3.14);}

ou

Como discutido anteriormente, invocações do mundo real são muitas vezes muito mais complicadas do que as simples peças aqui, então usar um std::invoke ou uma macro irá tipicamente melhorar a capacidade de escrita e leitura do seu código.

Posso converter um ponteiro em função de membro para um vazio*?

Não!

Detalhes técnicos: ponteiros para funções de membro e ponteiros para dados não são necessariamente representados da mesma forma. O apoio a uma função de membro pode ser uma estrutura de dados em vez de um ponteiro único. Pense sobre isso: se estiver apontando para uma função virtual, pode não estar apontando para uma pilha de código estaticamente resolúvel, então pode nem mesmo ser um endereço normal – pode ser uma estrutura de dados diferente de algum tipo.

Por favor não me envie um email se o acima parece funcionar na sua versão particular do seu compilador em seu sistema operacional particular. Eu não me importo. É ilegal, ponto.

Posso converter um ponteiro-para-função para um vazio*?

Não!

Pormenores técnicos: void*Apontadores são apontadores para dados, e os apontadores de funções apontam para funções. A linguagem não requer que funções e dados estejam no mesmo espaço de endereço, então, a título de exemplo e sem limitação, onarquiteturas que os têm em diferentes espaços de endereço, os dois tipos de ponteiro diferentes não serão comparáveis.

Por favor não me envie um e-mail se o acima parece funcionar na sua versão particular do seu compilador particular no seu sistema operacional particular. Eu não me importo. É ilegal, ponto.

Preciso de algo parecido com os pontos de função, mas com mais flexibilidade e/ou thread-safety; existe outra forma?

Utilizar um functionoid.

O que raio é um functionoid, e porque usaria um?

Functionoids são funções em esteróides. Functionoids são estritamente mais poderosos que funções, e que poderes extras resolvem alguns (não todos) dos desafios tipicamente enfrentados quando você usa function-pointers.

Vejamos o trabalho de um exemplo mostrando um uso tradicional de function-pointers, então vamos traduzir esse exemplo em function-pointers. A idéia tradicional dos ponteiros de funções é ter um monte de funções compatíveis:

int funct1( /*...params...*/ ) { /*...code...*/ }int funct2( /*...params...*/ ) { /*...code...*/ }int funct3( /*...params...*/ ) { /*...code...*/ }

Então você acessa aqueles por ponteiros de funções:

typedef int(*FunctPtr)( /*...params...*/ );void myCode(FunctPtr f){ // ... f( /*...args-go-here...*/ ); // ...}

Por vezes as pessoas criam um array desses ponteiros de funções:

FunctPtr array;array = funct1;array = funct1;array = funct3;array = funct2;// ...

Em cujo caso eles chamam a função acessando o array:

array( /*...args-go-here...*/ );

Com functionoids, você primeiro cria uma classe base com um método puro-virtual:

Então em vez de três funções, você cria três classes derivadas:

Então em vez de passar um ponteiro de função, você passa um Funct*. Eu criarei um typedef chamado FunctPtr apenas tomake o resto do código semelhante à antiga abordagem:

typedef Funct* FunctPtr;void myCode(FunctPtr f){ // ... f->doit( /*...args-go-here...*/ ); // ...}

Você pode criar um array deles quase da mesma maneira:

Isso nos dá a primeira dica sobre onde os functionoids são estritamente mais poderosos que os function-pointers: o fato de que a abordagem functionoid tem argumentos que você pode passar para os ctors (mostrado acima como …ctor-args…) enquanto a versão function-pointers não tem. Pense em um objeto functionoid como uma chamada de função liofilizada (ênfase na palavra chamada). Ao contrário de um ponteiro para uma função, um functionoid é (conceitualmente) um ponteiro para uma função parcialmente chamada. Imagine por enquanto uma tecnologia que lhe permite passar alguns argumentos, mas não todos, para uma função, depois permite-lhe congelar e secar essa chamada (parcialmente completada). Finja que essa tecnologia lhe devolve algum tipo de ponteiro mágico para uma chamada de função liofilizada parcialmente concluída. Depois você passa os args restantes usando esse ponteiro, e o sistema pega magicamente seus args originais (que foram liofilizados), combina-os com qualquer variável local que a função calculou antes de ser liofilizada, combina tudo isso com os args recém passados, e continua a execução da função onde ela parou quando foi liofilizada. Isso pode soar como ficção científica, mas é conceitualmente o que as funções permitem que você faça. Além disso, eles permitem que você “complete” repetidamente essa função liofilizada – sempre com vários “parâmetros restantes” diferentes, quantas vezes você quiser. Além disso, eles permitem (não exigem) que você mude o estado liofilizado quando ele é chamado, o que significa que os functionoids podem se lembrar de informações de uma chamada para a próxima.

Vamos colocar os pés de volta no chão e vamos trabalhar alguns exemplos para explicar o que todo esse jumbo realmente significa.

Suponha que as funções originais (no estilo antiquado function-pointer) tomaram parâmetros ligeiramente diferentes.

int funct1(int x, float y){ /*...code...*/ }int funct2(int x, const std::string& y, int z){ /*...code...*/ }int funct3(int x, const std::vector<double>& y){ /*...code...*/ }

Quando os parâmetros são diferentes, a abordagem antiquada dos ponteiros de funções é difícil de usar, uma vez que os chamados não sabem que parâmetros passar (o chamador apenas tem um ponteiro para a função, não o nome da função ou, quando os parâmetros são diferentes, o número e tipos de seus parâmetros) (não me escreva um e-mail sobre isso; sim, você pode fazer isso, mas você tem que ficar de cabeça erguida e fazer coisas confusas; mas não me escreva sobre isso – usefunctionoids em vez disso).

Com os functionoids, a situação é, pelo menos às vezes, muito melhor. Como um functionoid pode ser pensado como uma chamada de função afreeze-dried, basta pegar os args não comuns, como os que eu chamei y e/ou z, e fazer os respectivos ctors. Você também pode passar os args comuns (neste caso o int chamado x) para o ctor, mas você não precisa – você tem a opção de passar para o método puro virtual doit() em vez disso. Eu vou assumir que você quer passar x para doit() e y e/ou z para os ctors:

class Funct {public: virtual int doit(int x) = 0;};

Então ao invés de três funções, você cria três classes derivadas:

Agora você vê que os parâmetros do ctor ficam liofilizados no functionoid quando você cria o array offunctionoids:

Então quando o usuário invoca o doit() em uma dessas functionoids, ele fornece os args “restantes”, e o callconceptualmente combina os args originais passados para o ctor com aqueles passados para o método doit():

array->doit(12);

Como eu já sugeri, um dos benefícios dos functionoids é que você pode ter várias instâncias de, digamos, Funct1 em seu array, e essas instâncias podem ter parâmetros diferentes liofilizados nelas. Por exemplo, array earray são ambas do tipo Funct1, mas o comportamento de array->doit(12) será diferente do comportamento de array->doit(12), já que o comportamento dependerá tanto dos 12 que foram passados para doit() quanto dos args passados para os ctors.

Um outro benefício dos functionoids é aparente se mudarmos o exemplo de um array de functionoids para um localfunctionoid. Para definir o cenário, vamos voltar à abordagem antiquada function-pointer, e imaginar que você está tentando passar uma comparação-função para uma rotina sort() ou binarySearch(). A sort() ou binarySearch() rotina é chamada de childRoutine() e a função comparação-ponteiro tipo é chamada de FunctPtr:

void childRoutine(FunctPtr f){ // ... f( /*...args...*/ ); // ...}

Então diferentes chamadores passariam por diferentes funções-pontos, dependendo do que eles achassem melhor:

void myCaller(){ // ... childRoutine(funct1); // ...}void yourCaller(){ // ... childRoutine(funct3); // ...}

Podemos facilmente traduzir este exemplo em um usando functionoids:

Dando este exemplo como pano de fundo, podemos ver dois benefícios dos functionoids sobre os function-pointers. O benefício “ctor args” descrito acima, mais o fato de que os functionoids podem manter o estado entre chamadas de uma forma segura para os pontos de função. Entretanto, os dados estáticos não são intrinsecamente seguros – os dados estáticos são compartilhados entre todas as threads. A abordagem functionoid fornece algo que é intrinsecamente seguro para threads, uma vez que o código acaba com dados thread-local. A implementação é trivial: altere o dado estático antiquado para uma instância de dados dentro do objeto da functionoid this, epoof, os dados não são apenas thread-local, mas são mesmo seguros com chamadas recursivas: cada chamada para yourCaller() terá seu próprio objeto distinto Funct3 com seus próprios dados de instância distintos.

Note que nós ganhamos algo sem perder nada. Se você quer dados globais de thread, as functionoids podem dar ao youthat também: basta mudá-lo de um membro de dados de instância dentro do objeto functionoid this para um membro de dados estáticos dentro da classe functionoid, ou mesmo para um dado estático de escopo local. Você não estaria melhor do que com pontos de função, mas também não estaria pior.

A abordagem functionoid lhe dá uma terceira opção que não está disponível com a abordagem antiquada: thefunctionoid permite que os chamadores decidam se querem dados thread-local ou thread-global. Eles seriam responsáveis por uselocks nos casos em que eles quisessem dados thread-global, mas pelo menos eles teriam a escolha. É fácil:

Functionoids não resolvem todos os problemas encontrados ao fazer software flexível, mas eles são estritamente mais poderosos que os pontos de função e valem pelo menos a pena avaliar. Na verdade você pode facilmente provar que os functionoids não perdem nenhum poder sobre os function-pointers, já que você pode imaginar que a abordagem antiquada dos function-pointers é equivalente a ter um objeto global(!) functionoid. Uma vez que você pode sempre fazer um objeto global de function-pointers, você não perdeu nenhum terreno. QED.

Pode você fazer functionoids mais rápido que as chamadas de função normais?

Sim.

Se você tiver um functionoid pequeno, e no mundo real que é bastante comum, o custo da chamada de função pode ser alto comparado com o custo do trabalho feito pelo functionoid. Na FAQ anterior, os functionoids eram implementados usando funções virtuais e normalmente custam uma chamada de função. Uma abordagem alternativa usa modelos.

O exemplo a seguir é semelhante em espírito ao da FAQ anterior. Eu renomeei doit() para operator()() para melhorar a legibilidade do código de chamada e para permitir que alguém passe um ponteiro de função regular:

A diferença entre esta abordagem e a da FAQ anterior é que o fuctionoid fica “vinculado” à chamada em tempo de compilação e não em tempo de execução. Pense nisso como passando em um parâmetro: se você conhece em tempo de compilação o tipo de functionoid que você finalmente quer passar, então você pode usar a técnica acima, e você pode, pelo menos em casos típicos, obter um benefício de velocidade tendo o compilador em linha – expandir o código do functionoid dentro da chamada. Aqui está um exemplo:

template <typename FunctObj>void myCode(FunctObj f){ // ... f( /*...args-go-here...*/ ); // ...}

Quando o compilador compila o acima, ele pode inline-expandir a chamada que pode melhorar a performance.

Aqui está uma maneira de chamar o acima:

void blah(){ // ... Funct2 x("functionoids are powerful", 42); myCode(x); // ...}

Aside: como foi sugerido no primeiro parágrafo acima, você também pode passar os nomes das funções normais (embora você possa incorrer no custo da chamada de função quando o chamador usa estas):

void myNormalFunction(int x);void blah(){ // ... myCode(myNormalFunction); // ...}

Qual é a diferença entre um functionoid e um functor?

Um functionoid é um objeto que tem um método principal. É basicamente a extensão OO de uma função tipo C como aprintf(). Uma pessoa usaria uma functionoid sempre que a função tem mais de um ponto de entrada (ou seja, mais de um “método”) e/ou precisa manter o estado entre chamadas de forma segura (a abordagem estilo C para manter o estado entre chamadas é adicionar uma variável local “estática” à função, mas isso é horrivelmente inseguro em um ambiente multi-tarefa).

Um functor é um caso especial de uma functionoid: é uma functionoid cujo método é o “function-call operator, “operator()(). Como ele sobrecarrega o operador de chamada de função, o código pode chamar seu método principal usando a mesma sintaxe que eles usariam para uma chamada de função. Por exemplo, se “foo” é um funcionário, para chamar o método “operator()()” no objeto “foo”, se diria “foo()”. O benefício disso está nos modelos, desde então o modelo pode ter um parâmetro de modelo que será usado como uma função, e este parâmetro pode ser ou o nome de uma função ou um functor-objeto. Há uma vantagem de performance em ser um objeto funtor, uma vez que o método “operator()()” pode ser alinhado (enquanto que se você passar o endereço de uma função ele deve, necessariamente, ser não alinhado).

Isso é muito útil para coisas como a função “comparação” em containers ordenados. Em C, a função de comparação é sempre passada pelo ponteiro (por exemplo veja a assinatura para “qsort()”), mas em C++ o parâmetro pode vir tanto como apointer para a função OU como o nome de um funcio-objeto, e o resultado é que containers ordenados em C++ podem ser, em alguns casos, muito mais rápidos (e nunca mais lentos) do que o equivalente em C.

Desde que Java não tem nada semelhante a templates, ele deve usar binding dinâmico para tudo isso, e binding dinâmico denecessidade significa uma chamada de função. Normalmente não é uma grande coisa, mas em C++ queremos habilitar um código de desempenho extremamente alto. Ou seja, C+++ tem uma filosofia de “pagar por ele apenas se você o usar”, o que significa que a linguagem nunca deve arbitrariamente impor qualquer sobrecarga sobre o que a máquina física é capaz de executar (é claro que um programador pode, opcionalmente, usar técnicas como o binding dinâmico que irá, em geral, impor alguma sobrecarga em troca de flexibilidade ou alguma outra “doença”, mas cabe ao designer e ao programador decidir se eles querem os benefícios (e custos) de tais construções).

Leave a Reply