paulo1205
(usa Ubuntu)
Enviado em 11/08/2016 - 09:02h
Sobrecarregar o operador de chamada de função é muito útil, e o mais legal é que, ao contrário dos demais operadores, que são todos unários ou binários, você pode passar quantos argumentos quiser e dos tipos que quiser.
Um uso muito comum é para criar classes de objetos com comportamento equivalente a funções. Um caso particular disso é para criar predicados (que são funções booleanas que retornam verdadeiro se uma determinada condição for verdadeira, a partir de testes sobre seus argumentos). Em C++, esses predicados costumam ser usados com algoritmos encontrados na biblioteca padrão, no cabeçalho <algorithm>.
Suponha que você tem uma coleção de números inteiros guardada em
int_col, que é do tipo
std::vector<int>, e que você queira copiar para uma segunda coleção
int_col2 todos os números em
int_col que sejam múltiplos de 3, usando um algoritmo de <algorithm> que invoca um predicado. Eis abaixo um jeito de fazer, indicando uma função, por meio de um ponteiro de função, como predicado.
#include <algorithm>
#include <vector>
bool is_mult_of_3(int n){ return !(n%3); } // Predicado.
void f(){
std::vector<int> int_col{ /* um monte de valores inteiros */};
std::vector<int> int_col2;
/* ... */
auto vi=int_col.cbegin();
while((vi=std::find_if(vi, int_col.cend(), is_mult_of_3))!=int_col.cend())
int_col2.push_back(*vi);
/* ... */
}
Eu usei uma função porque o predicado compara o argumento sempre com um valor fixo. Como eu usei
std::find_if(), o predicado só pode receber um argumento. Mas se eu quisesse testar se o valor é múltiplo de um valor arbitrário, que eu pudesse definir a qualquer momento, mesmo durante a execução do programa, como eu poderia fazer?
Um jeito é criar uma classe com um construtor que receba as condições “fixas” do pedicado como argumento na hora de criar o objeto, e definir uma sobrecarga do operador
() para esse objeto, de modo que ele possa ser usado como se fosse uma função (no nosso caso, com o papel de predicado). Veja como.
#Include <algorithm>
#include <vector>
class is_mult {
private:
int _n;
public:
is_multi(int n): _n(n) { }
bool operator()(int n){ return !(n%_n); }
};
void f(int n){
std::vector<int> int_col{ /* um monte de valores inteiros */};
std::vector<int> int_col2;
is_mult is_mult_of_n(n); // Por meio do construtor, defino como ai funcionar o predicado.
/* ... */
auto vi=int_col.cbegin();
// Agora, em vez de ponteiro de função, eu passo (uma referência a) o objeto.
while((vi=std::find_if(vi, int_col.cend(), is_mult_of_n))!=int_col.cend())
int_col2.push_back(*vi);
/* ... */
}
Se você já ouviu falar de lambdas no C++11, saiba que eles nada mais são do que objetos-função de classes anônimas, criadas automaticamente. O código da função
g(), abaixo, produz o mesmo efeito da função
f() mostrada acima, e o lambda gera uma classe anônima rigorosamente equivalente à classe
is_mult.
void g(int n){
std::vector<int> int_col{ /* um monte de valores inteiros */};
std::vector<int> int_col2;
/*
O “n” dentro dos colchetes cria um membro anônimo na
classe e um construtor que copia o valor de n para esse
membro anônimo. Quando usado no corpo do lambda, o
“n” é substituído não pelo valor que n tem dentro de g(),
mas sim pelo membro anônimo da classe correspondente
ao argumento n passado ao construtor.
*/
auto is_mult_of_n=[n](int m){ return !(m%n); };
/* ... */
auto vi=int_col.cbegin();
while((vi=std::find_if(vi, int_col.cend(), is_mult_of_n))!=int_col.cend())
int_col2.push_back(*vi);
/* ... */
}
Ou ainda o seguinte, também rigorosamente equivalente, mas com o lambda expresso diretamente no invocação da função que vai usá-lo como argumento.
void g(int n){
std::vector<int> int_col{ /* um monte de valores inteiros */};
std::vector<int> int_col2;
/* ... */
auto vi=int_col.cbegin();
while((vi=std::find_if(vi, int_col.cend(), [n](int m){ return !(m%n); }))!=int_col.cend())
int_col2.push_back(*vi);
/* ... */
}
(Veja também o tópico sobre lambda que tivemos aqui há pouco tempo:
https://www.vivaolinux.com.br/topico/C-C++/Funcao-Lambda-Em-C.)
Mas predicados, em particular, ou objetos-função, em geral, não esgotam o uso do operador
().
Sempre que, por uma questão de notação, o nome do objeto puder ser aplicado a um conjunto de argumentos diretamente, pode-se usar operador
() para não ter de inventar um método com um nome redundante.
Um exemplo que eu já vi e já usei foi o de uma classe de polinômios. Na Matemática, quando você define um polinômio, o faz com algo numa forma parecida com isto “
P(x)=2x²-4x+5”, e quando quer indicar o valor do polinômio no quando
x vale, por exemplo, 1.2, você diz apenas “
P(1.2)”. Em C++, com a sobrecarga de
operator()()você pode conseguir uma notação muito próxima dessa notação matemática.
polynomial<double> P{2, -4, 5}; // P(x)=2x²-4x+5;
/* ... */
std::cout << "P(1.2)=" << P(1.2); // Calcula e imprime o polinômio P(x) quando x=1.2;
Sem a sobrecarga, talvez nós tivéssemos de inventar um método chamado
evaluate_at(), e usar algo como
P.evaluate_at(1.2), que além de fugir da notação matemática tradicional, também é mais cansativo de escrever e de ler.
Outro uso que eu já vi e já usei foi como método de acesso a elemento em classes que representam arrays multidimensionais, em que você não deseja dar ao usuário acesso a níveis separados do array. Compare os dois exemplos abaixo para entender.
// Array 3D, modo 1: array de array de array.
#include <array>
std::array<std::array<std::array<double, Z_size>, Y_size>, X_size> array3d;
// Poderia ser std::vector<std::vector<std::vector<double>>>, mas seria pior ainda!
void f(){
std::cout << array3d[x][y][z] << '\n'; // Acesso a um elemento. OK.
std::cout << array3d[x][y].data() << '\n'; // Acesso a um array inteiro. Isso é OK?
std::cout << array3d[x].data() << '\n'; // Acesso a um array de arrays. Isso é OK?
std::cout << array3d.data() << '\n'; // Acesso bruto a todos os dados. Isso é OK?
// Com vector em lugar de arrays, seria pior ainda, porque cada linha, coluna ou
// profundidade poderia ter números de elementos diferentes dos demais!!
}
// Modo 2: com uma classe que encapsula totalmente a representação do array 3D.
// Com o encapsulamento, perde-se a possibilidade de indexar os elementos
// com múltiplos níveis de colchetes, de modo que se usa o operador ().
template <unsigned X_sz, unsigned Y_sz, unsigned Z_sz, typename T>
class array3d_t {
private:
T [X_sz*Y_sz*Z_sz] _data;
public:
T &operator()(unsigned x, unsigned y, unsigned z){ return _data[(x*Y_sz+y)*Z_sz+z]; }
};
array3d_t<X_size, Y_size, Z_size, double> array3d;
void f(){
std::cout << array3d(x, y, z) << '\n'; // Acesso somente a cada elemento, nunca a subarrays.
}
Mas a sua imaginação é o limite. O fato de permitir qualquer quantidade de elementos de quaisquer tipos dá a você inúmeras possibilidades.
Eu só recomendo cautela para que a notação faça sentido. Espera-se que a sobrecarga operador
() -- como, aliás, a de qualquer operador -- ajude o código a ser naturalmente mais inteligível, não que o torne mais obscuro.